摘要:既然的構(gòu)造方法是復(fù)制新的數(shù)組,那么是為什么呢這里提前透露一下結(jié)論數(shù)組元素為對象時,實際上存儲的是對象的引用,進行數(shù)組復(fù)制也只是復(fù)制了對象的引用。即數(shù)組元素為對象時,實際上存儲的是對象的引用。
前言
事件起因是由于同事使用ArrayList的帶參構(gòu)造方法進行ArrayList對象復(fù)制,修改新的ArrayList對象中的元素(對象)的成員變量時也會修改原ArrayList中的元素(對象)的成員變量。
下面會通過復(fù)盤代碼向大家重現(xiàn)遇到的問題
復(fù)盤代碼 用戶類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; } }問題重現(xiàn)示例
import java.util.ArrayList; import java.util.List; public class ArrayListReference { public static void main(String[] args) { // 原用戶列表 List示例運行結(jié)果users = new ArrayList<>(); for (int i = 0; i < 10; i++) { users.add(new User(i, "test")); } // 新用戶列表 List 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的構(gòu)造方法重新構(gòu)造一個新的ArrayList后,操作新ArrayList對象中的元素時會影響到原來的ArrayList中的元素呢?
首先需要分析ArrayList的構(gòu)造方法
ArrayList源碼分析下面是示例中調(diào)用的ArrayList構(gòu)造方法的源碼
public ArrayList(Collection extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { if (elementData.getClass() != Object[].class) // 此處為關(guān)鍵代碼,此處就是數(shù)組元素的復(fù)制方法 elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this.elementData = EMPTY_ELEMENTDATA; } }
從源碼中得知數(shù)組復(fù)制的關(guān)鍵代碼為
elementData = Arrays.copyOf(elementData, size, Object[].class);
下面進入Arrays.copyOf()的源碼進行研究
public staticT[] copyOf(U[] original, int newLength, Class extends T[]> newType) { @SuppressWarnings("unchecked") // 構(gòu)造一個新的數(shù)組對象 T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); // 將原數(shù)組元素復(fù)制到新數(shù)組中 System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
從上面的源碼得知關(guān)鍵代碼為
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方法,很難跟蹤其實現(xiàn)代碼。不過可以從方法注釋中可以知道這個方法的特點:
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.
翻譯結(jié)果為
將數(shù)組從指定的源數(shù)組(從指定位置開始)復(fù)制到目標(biāo)數(shù)組的指定位置。將數(shù)組組件的子序列從src引用的源數(shù)組復(fù)制到dest引用的目標(biāo)數(shù)組,復(fù)制的組件數(shù)量等于length參數(shù)。源數(shù)組中通過srcPos+length-1位置的組件分別復(fù)制到目標(biāo)數(shù)組中通過destPos+length-1位置的destPos。
既然ArrayList的構(gòu)造方法是復(fù)制新的數(shù)組,那么是為什么呢?這里提前透露一下結(jié)論:數(shù)組元素為對象時,實際上存儲的是對象的引用,ArrayList進行數(shù)組復(fù)制也只是復(fù)制了對象的引用。所以才會出現(xiàn)一開始說的問題
再次驗證下面將會使用一個數(shù)組的復(fù)制示例驗證結(jié)論,使用==來比較對象引用是否相同
問題重現(xiàn)示例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])); } } }示例運行結(jié)果
0:true 1:true 2:true 3:true 4:true 5:true 6:true 7:true 8:true 9:true結(jié)果分析
從運行結(jié)果中可以得知,上面提出的結(jié)論是正確的。即數(shù)組元素為對象時,實際上存儲的是對象的引用。
解決辦法解決方法很簡單,只需要遍歷對象數(shù)組中的元素,調(diào)用對象的構(gòu)造方法構(gòu)造新的對象并加入新的數(shù)組中即可
解決辦法示例public class ArrayListReferenceSolution { public static void main(String[] args) { // 原用戶列表 List示例運行結(jié)果users = new ArrayList<>(); for (int i = 0; i < 10; i++) { users.add(new User(i, "test")); } // 新用戶列表 List newUsers = new ArrayList<>(); for (int j = 0; j < users.size(); j++) { // 使用構(gòu)造方法構(gòu)造新的對象 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結(jié)果分析
從運行結(jié)果可以得知,使用示例中的方法就可以復(fù)制出一個不會干擾原ArrayList的對象。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/74779.html
摘要:靜態(tài)變量是被泛型類的所有實例所共享的。所以引用能完成泛型類型的檢查。對于這個類型系統(tǒng),有如下的一些規(guī)則相同類型參數(shù)的泛型類的關(guān)系取決于泛型類自身的繼承體系結(jié)構(gòu)。事實上,泛型類擴展都不合法。 前言 和C++以模板來實現(xiàn)靜多態(tài)不同,Java基于運行時支持選擇了泛型,兩者的實現(xiàn)原理大相庭徑。C++可以支持基本類型作為模板參數(shù),Java卻只能接受類作為泛型參數(shù);Java可以在泛型類的方法中取得...
摘要:引用泛型除了方法因不能使用外部實例參數(shù)外,其他繼承實現(xiàn)成員變量,成員方法,方法返回值等都可使用。因此,生成的字節(jié)碼僅包含普通的類,接口和方法。 為什么要使用泛型程序設(shè)計? 一般的類和方法,只能使用具體的類型:要么是基本類型,要么是自定義類的對應(yīng)類型;如果要編寫可以應(yīng)用于多種類型的代碼,這種刻板的限制對代碼的束縛就會很大。----摘自原書Ordinary classes and meth...
摘要:第三階段常見對象的學(xué)習(xí)集合框架概述和集合的遍歷一集合框架的概述集合的由來如果一個程序只包含固定數(shù)量的且其生命周期都是已知的對象,那么這是一個非常簡單的程序。進而它們的遍歷方式也應(yīng)該是不同的,最終就沒有定義迭代器類。 第三階段 JAVA常見對象的學(xué)習(xí) 集合框架概述和集合的遍歷 (一) 集合框架的概述 (1) 集合的由來 如果一個程序只包含固定數(shù)量的且其生命周期都是已知的對象,那么這是一...
摘要:自定義類的概述自定義類的概述代碼映射成現(xiàn)實事物的過程就是定義類的過程。自定義類的格式自定義類的格式使用類的形式對現(xiàn)實中的事物進行描述。 01引用數(shù)據(jù)類型_類 * A: 數(shù)據(jù)類型 * a: java中的數(shù)據(jù)類型分為:基本類型和引用類型 * B: 引用類型的分類 * a: Java為我們提供好的類,比如說:Scanner,Random等。 * b: 我們自己創(chuàng)建的類...
摘要:網(wǎng)站的面試專題學(xué)習(xí)筆記非可變性和對象引用輸出為,前后皆有空格。假定??臻g足夠的話,盡管遞歸調(diào)用比較難以調(diào)試,在語言中實現(xiàn)遞歸調(diào)用也是完全可行的。棧遵守規(guī)則,因此遞歸調(diào)用方法能夠記住調(diào)用者并且知道此輪執(zhí)行結(jié)束之返回至當(dāng)初的被調(diào)用位置。 ImportNew 網(wǎng)站的Java面試專題學(xué)習(xí)筆記 1. 非可變性和對象引用 String s = Hello ; s += World ; s.tr...
閱讀 2525·2023-04-26 02:47
閱讀 3008·2023-04-26 00:42
閱讀 875·2021-10-12 10:12
閱讀 1382·2021-09-29 09:35
閱讀 1697·2021-09-26 09:55
閱讀 486·2019-08-30 14:00
閱讀 1540·2019-08-29 12:57
閱讀 2359·2019-08-28 18:00