摘要:既然的構造方法是復制新的數組,那么是為什么呢這里提前透露一下結論數組元素為對象時,實際上存儲的是對象的引用,進行數組復制也只是復制了對象的引用。即數組元素為對象時,實際上存儲的是對象的引用。
前言
事件起因是由于同事使用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; } }問題重現示例
import java.util.ArrayList; import java.util.List; public class ArrayListReference { public static void main(String[] args) { // 原用戶列表 List示例運行結果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的構造方法重新構造一個新的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; } }
從源碼中得知數組復制的關鍵代碼為
elementData = Arrays.copyOf(elementData, size, Object[].class);
下面進入Arrays.copyOf()的源碼進行研究
public staticT[] 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; }
從上面的源碼得知關鍵代碼為
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示例運行結果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++) { // 使用構造方法構造新的對象 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的對象。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74779.html
摘要:靜態變量是被泛型類的所有實例所共享的。所以引用能完成泛型類型的檢查。對于這個類型系統,有如下的一些規則相同類型參數的泛型類的關系取決于泛型類自身的繼承體系結構。事實上,泛型類擴展都不合法。 前言 和C++以模板來實現靜多態不同,Java基于運行時支持選擇了泛型,兩者的實現原理大相庭徑。C++可以支持基本類型作為模板參數,Java卻只能接受類作為泛型參數;Java可以在泛型類的方法中取得...
摘要:引用泛型除了方法因不能使用外部實例參數外,其他繼承實現成員變量,成員方法,方法返回值等都可使用。因此,生成的字節碼僅包含普通的類,接口和方法。 為什么要使用泛型程序設計? 一般的類和方法,只能使用具體的類型:要么是基本類型,要么是自定義類的對應類型;如果要編寫可以應用于多種類型的代碼,這種刻板的限制對代碼的束縛就會很大。----摘自原書Ordinary classes and meth...
摘要:第三階段常見對象的學習集合框架概述和集合的遍歷一集合框架的概述集合的由來如果一個程序只包含固定數量的且其生命周期都是已知的對象,那么這是一個非常簡單的程序。進而它們的遍歷方式也應該是不同的,最終就沒有定義迭代器類。 第三階段 JAVA常見對象的學習 集合框架概述和集合的遍歷 (一) 集合框架的概述 (1) 集合的由來 如果一個程序只包含固定數量的且其生命周期都是已知的對象,那么這是一...
摘要:自定義類的概述自定義類的概述代碼映射成現實事物的過程就是定義類的過程。自定義類的格式自定義類的格式使用類的形式對現實中的事物進行描述。 01引用數據類型_類 * A: 數據類型 * a: java中的數據類型分為:基本類型和引用類型 * B: 引用類型的分類 * a: Java為我們提供好的類,比如說:Scanner,Random等。 * b: 我們自己創建的類...
摘要:網站的面試專題學習筆記非可變性和對象引用輸出為,前后皆有空格。假定棧空間足夠的話,盡管遞歸調用比較難以調試,在語言中實現遞歸調用也是完全可行的。棧遵守規則,因此遞歸調用方法能夠記住調用者并且知道此輪執行結束之返回至當初的被調用位置。 ImportNew 網站的Java面試專題學習筆記 1. 非可變性和對象引用 String s = Hello ; s += World ; s.tr...
閱讀 2508·2023-04-26 02:47
閱讀 2999·2023-04-26 00:42
閱讀 865·2021-10-12 10:12
閱讀 1372·2021-09-29 09:35
閱讀 1689·2021-09-26 09:55
閱讀 478·2019-08-30 14:00
閱讀 1532·2019-08-29 12:57
閱讀 2350·2019-08-28 18:00