事件原由是因为同事使用ArrayList
的带参构造方法进行ArrayList
对象复制,修改新的ArrayList
对象中的元素(对象)的成员变量时也会修改原ArrayList
中的元素(对象)的成员变量。java
下面会经过复盘代码向你们重现遇到的问题数组
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; } }
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); } }
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'}]
为何使用了ArrayList
的构造方法从新构造一个新的ArrayList
后,操做新ArrayList
对象中的元素时会影响到原来的ArrayList
中的元素呢?ide
首先须要分析ArrayList
的构造方法源码分析
下面是示例中调用的ArrayList
构造方法的源码this
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; } }
从源码中得知数组复制的关键代码为翻译
elementData = Arrays.copyOf(elementData, size, Object[].class);
下面进入Arrays.copyOf()
的源码进行研究code
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; }
从上面的源码得知关键代码为component
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
如下为System.arraycopy()
方法的源码对象
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
因为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])); } } }
0:true 1:true 2:true 3:true 4:true 5:true 6:true 7:true 8:true 9:true
从运行结果中能够得知,上面提出的结论是正确的。即数组元素为对象时,实际上存储的是对象的引用。
解决方法很简单,只须要遍历对象数组中的元素,调用对象的构造方法构造新的对象并加入新的数组中便可
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))); } } }
0:false 1:false 2:false 3:false 4:false 5:false 6:false 7:false 8:false 9:false
从运行结果能够得知,使用示例中的方法就能够复制出一个不会干扰原ArrayList
的对象。