Java中ArrayList的對象引用問題


前言
事件起因是由於同事使用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的對象。
---------------------


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM