事件原由是因爲同事使用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
的對象。