前言
事件起因是由于同事使用ArrayList的带参构造方法进行ArrayList对象复制,修改新的ArrayList对象中的元素(对象)的成员变量时也会修改原ArrayList中的元素(对象)的成员变量。

下面会通过复盘代码向大家重现遇到的问题

复盘代码
用户类
public class User {

private Integer id;

private String name;

public User(Integer id, String name) {
this.id = id;
this.name = name;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
问题重现示例
import java.util.ArrayList;
import java.util.List;

public class ArrayListReference {

public static void main(String[] args) {
// 原用户列表
List<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add(new User(i, "test"));
}
// 新用户列表
List<User> newUsers = new ArrayList<>(users);
for (int j = 0; j < newUsers.size(); j++) {
// 修改新用户列表的用户名
newUsers.get(j).setName(String.valueOf(j));
}
// 打印新用户列表
System.out.println("newUsers:" + newUsers);
// 重新打印原用户列表
System.out.println("After update newUsers,users:" + users);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
示例运行结果
users:[User{id=0, name='test'}, User{id=1, name='test'}, User{id=2, name='test'}, User{id=3, name='test'}, User{id=4, name='test'}, User{id=5, name='test'}, User{id=6, name='test'}, User{id=7, name='test'}, User{id=8, name='test'}, User{id=9, name='test'}]
newUsers:[User{id=0, name='0'}, User{id=1, name='1'}, User{id=2, name='2'}, User{id=3, name='3'}, User{id=4, name='4'}, User{id=5, name='5'}, User{id=6, name='6'}, User{id=7, name='7'}, User{id=8, name='8'}, User{id=9, name='9'}]
After update newUsers,users:[User{id=0, name='0'}, User{id=1, name='1'}, User{id=2, name='2'}, User{id=3, name='3'}, User{id=4, name='4'}, User{id=5, name='5'}, User{id=6, name='6'}, User{id=7, name='7'}, User{id=8, name='8'}, User{id=9, name='9'}]
1
2
3
分析
问题
为什么使用了ArrayList的构造方法重新构造一个新的ArrayList后,操作新ArrayList对象中的元素时会影响到原来的ArrayList中的元素呢?

首先需要分析ArrayList的构造方法

ArrayList源码分析
下面是示例中调用的ArrayList构造方法的源码

public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
// 此处为关键代码,此处就是数组元素的复制方法
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
1
2
3
4
5
6
7
8
9
10
从源码中得知数组复制的关键代码为

elementData = Arrays.copyOf(elementData, size, Object[].class);
1
下面进入Arrays.copyOf()的源码进行研究

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
// 构造一个新的数组对象
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
// 将原数组元素复制到新数组中
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
1
2
3
4
5
6
7
8
9
10
从上面的源码得知关键代码为

System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
1
以下为System.arraycopy()方法的源码

public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
1
由于System.arraycopy()方法为native方法,很难跟踪其实现代码。不过可以从方法注释中可以知道这个方法的特点:

Copies an array from the specified source array, beginning at the specified position, to the specified position of the destination array. A subsequence of array components are copied from the source array referenced by src to the destination array referenced by dest. The number of components copied is equal to the length argument. The components at positions srcPos through srcPos+length-1 in the source array are copied into positions destPos through destPos+length-1, respectively, of the destination array.

翻译结果为

将数组从指定的源数组(从指定位置开始)复制到目标数组的指定位置。将数组组件的子序列从src引用的源数组复制到dest引用的目标数组,复制的组件数量等于length参数。源数组中通过srcPos+length-1位置的组件分别复制到目标数组中通过destPos+length-1位置的destPos。

既然ArrayList的构造方法是复制新的数组,那么是为什么呢?这里提前透露一下结论:数组元素为对象时,实际上存储的是对象的引用,ArrayList进行数组复制也只是复制了对象的引用。所以才会出现一开始说的问题

再次验证
下面将会使用一个数组的复制示例验证结论,使用==来比较对象引用是否相同

问题重现示例
import java.util.Arrays;

public class ArrayReference {

public static void main(String[] args) {
// 原用户列表
User[] users = new User[10];
for (int i = 0; i < users.length; i++) {
users[i] = (new User(i, "test"));
}
// 新用户列表
User[] newUsers = Arrays.copyOf(users, users.length);
for (int j = 0; j < users.length; j++) {
// 比较对象引用
System.out.println(j + ":" + (users[j] == newUsers[j]));
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
示例运行结果
0:true
1:true
2:true
3:true
4:true
5:true
6:true
7:true
8:true
9:true
1
2
3
4
5
6
7
8
9
10
结果分析
从运行结果中可以得知,上面提出的结论是正确的。即数组元素为对象时,实际上存储的是对象的引用。

解决办法
解决方法很简单,只需要遍历对象数组中的元素,调用对象的构造方法构造新的对象并加入新的数组中即可

解决办法示例
public class ArrayListReferenceSolution {

public static void main(String[] args) {
// 原用户列表
List<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add(new User(i, "test"));
}
// 新用户列表
List<User> newUsers = new ArrayList<>();
for (int j = 0; j < users.size(); j++) {
// 使用构造方法构造新的对象
newUsers.add(new User(users.get(j).getId(),users.get(j).getName()));
}
for (int k= 0; k < users.size(); k++) {
// 比较对象引用
System.out.println(k + ":" + (users.get(k) == newUsers.get(k)));
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
示例运行结果
0:false
1:false
2:false
3:false
4:false
5:false
6:false
7:false
8:false
9:false
1
2
3
4
5
6
7
8
9
10
结果分析
从运行结果可以得知,使用示例中的方法就可以复制出一个不会干扰原ArrayList的对象。
---------------------

Java中ArrayList的对象引用问题的更多相关文章

  1. java中ArrayList 、LinkList区别

    转自:http://blog.csdn.net/wuchuanpingstone/article/details/6678653 个人建议:以下这篇文章,是从例子说明的方式,解释ArrayList.L ...

  2. JAVA中ArrayList用法

    JAVA中ArrayList用法 2011-07-20 15:02:03|  分类: 计算机专业 |  标签:java  arraylist用法  |举报|字号 订阅     Java学习过程中做题时 ...

  3. Java中ArrayList与LinkedList的区别

    Java中ArrayList与LinkedList的区别 一般大家都知道ArrayList和LinkedList的区别: 1. ArrayList的实现是基于数组,LinkedList的实现是基于双向 ...

  4. Java中arraylist和linkedlist源代码分析与性能比較

    Java中arraylist和linkedlist源代码分析与性能比較 1,简单介绍 在java开发中比較经常使用的数据结构是arraylist和linkedlist,本文主要从源代码角度分析arra ...

  5. Java中对象、对象引用、堆、栈、值传递以及引用传递的详解

    Java中对象.对象引用.堆.栈.值传递以及引用传递的详解 1.对象和对象引用的差别: (1).对象: 万物皆对象.对象是类的实例. 在Java中new是用来在堆上创建对象用的. 一个对象能够被多个引 ...

  6. java中ArrayList 和 LinkedList 有什么区别

    转: java中ArrayList 和 LinkedList 有什么区别 ArrayList和LinkedList都实现了List接口,有以下的不同点:1.ArrayList是基于索引的数据接口,它的 ...

  7. Java中ArrayList和LinkedList差别

    一般大家都知道ArrayList和LinkedList的大致差别: 1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构.  2.对于随机訪问get和set.A ...

  8. Java中ArrayList和LinkedList区别 时间复杂度 与空间复杂度

    一般大家都知道ArrayList和LinkedList的大致区别:      1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构.      2.对于随机访问 ...

  9. Java中ArrayList的自我实现

    对于ArrayList相比大家都很熟悉,它是java中最常用的集合之一.下面就给出它的自我实现的java代码. 需要说明的一点是,它是基于数组创建的.所以它在内存中是顺序存储,对于查找十分的方便. p ...

随机推荐

  1. 自制Java虚拟机(四)-对象、new、invokespecial

    自制Java虚拟机(四)-对象.new.invokespecial 一.对象的表示 刚开始学Java的时候,图书馆各种教程,书名往往都是“Java面向对象高级编程”,通常作者都会与C++做个比较,列出 ...

  2. 遍历json字符串 并 写入对应的文本框

    1.js代码: function getFlws(){ var url = urlpath + "bhjk/getJson2.sd?"; $.post(url,function(d ...

  3. springBoot2.0 配置shiro实现权限管理

    一.前言 基于上一篇springBoot2.0 配置 mybatis+mybatisPlus+redis 这一篇加入shiro实现权限管理 二.shiro介绍 2.1 功能特点 Shiro 包含 10 ...

  4. EOS多主机多节点环境配置

    本文使用了四台同网段的主机,第一台做为eosio创世用户使用,另外三台做为出块者节点使用,最终实现了EOS多主机多节点的配置.最后EOSIO创世用户不再出块,由选举出来的各个节点轮流出块,下面将介绍具 ...

  5. Linux--7

    一.Nginx.conf主配置文件 Nginx主配置文件conf/nginx.conf是一个纯文本类型的文件,整个配置文件是以区块的形式组织的.一般,每个区块以一对大括号{}来表示开始与结束. 核心模 ...

  6. Docker从入门到实战(三)

    Docker从入门到实战(三) 一:安装Docker 1. linux系统脚本安装 Docker基于linux容器技术,面向服务器端,Docker只能安装运行在64位计算机上(社区有对32位的支持), ...

  7. json数据前台解析 修改check属性用prop()

    jQuery中的$.getJSON( )方法函数主要用来从服务器加载json编码的数据,它使用的是GET HTTP请求.使用方法如下: $.getJSON( url [, data ] [, succ ...

  8. 6 - 编码解码器-一种channelHandler

    6.1 解码器 6.1.1 抽象类-ByteToMessageDecoder decode(ChannelHandlerContext ctx, ByteBuf in, List<Object& ...

  9. ajax无刷新评论示例

    下面就为大家带来一篇 ajax无刷新评论示例.学习还是有点帮助的,给大家做个参考吧. 这是留言板的界面,当用户点击提交留言的时候,自动提交到我的留言下面 留言内容中为空,或者为灰色的“没有填写留言内容 ...

  10. hibernate log4j2输出sql带参数

    网上有很多是输出sql ,参数以?的形式,后面输出参数binding的log,还要自己拼接特别麻烦:这里整理下输出原生sql的方法.组件是log4jdbc 1: 修改pom.xml,确定有下面的配置, ...