前言
今天我在刷LeetCode的時候遇到了一個問題洽故,就是ArrayList添加不進去數(shù)據(jù),其實不是沒有添加進去盗誊,而是添加進去的數(shù)據(jù)被改變了时甚,為什么會改變了呢隘弊?其實涉及到ArrayList存放的是值還是引用的問題,網(wǎng)上有很多回答是:如果是基本數(shù)據(jù)類型則存放的是值荒适,如果是對象存放的就是引用梨熙。那么到底是什么呢,讓我們來一探究竟吧刀诬!
正文
原題在此:
39. 組合總和
給定一個無重復元素的數(shù)組
candidates
和一個目標數(shù)target
咽扇,找出candidates
中所有可以使數(shù)字和為target
的組合。
candidates
中的數(shù)字可以無限制重復被選取舅列。
我一開始寫的代碼是這樣的:
class Solution {
private List<List<Integer>> result = new ArrayList();
private ArrayList<Integer> solution = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
backtracking(candidates,target,0);
return result;
}
public void backtracking(int[] candidates, int residual, int start) {
if (residual < 0) {
return;
}
if (residual == 0) {
result.add(solution);
return;
}
for(int i = start;i<candidates.length;i++) {
solution.add(candidates[i]);
backtracking(candidates,residual-candidates[i],i);
solution.remove(solution.size()-1);
}
}
}
看了一遍肌割,沒什么問題,非常自信的點了運行帐要,結果懵逼了把敞,List里面什么也沒有
遇Bug沒關系,首先來分析一下:既然輸出是兩個 []榨惠,那么List中肯定添加過兩次數(shù)據(jù)奋早,也就是進入了if語句中,那么可能的原因就是代碼寫的有問題導致 solution中沒有數(shù)據(jù)赠橙,那就打印一下看看唄耽装。
修改代碼:
……
if (residual == 0) {
System.out.println(solution.toString()); //打印solution
result.add(solution);
return;
}
……
看一下控制臺,solution正常打印了期揪,而且結果是正確的
既然solution有正確的數(shù)據(jù)掉奄,那么問題就應該出現(xiàn)在List的添加上,那就再打印一下看看凤薛。
修改代碼:
……
if (residual == 0) {
result.add(solution);
System.out.println("----------");
for (List<Integer> integers : result) {
System.out.println(integers.toString()); //打印List中的內容
}
System.out.println("==========");
return;
}
……
從打印的內容上看姓建,第一次添加的是 [2,2,3],沒有問題缤苫。但是第二次的打印結果就很奇怪速兔,是兩個 [7],也就是我第一次添加的內容被修改了活玲。那么可能的原因只有一個涣狗,List中添加進去的是一個引用,而并非實際的值舒憾,不然結果怎么會改變呢镀钓。但是List中存放的真的是引用嗎?我們來進一步的驗證珍剑。
首先我們將ArrayList中的泛型指定為基本數(shù)據(jù)類型的包裝類和我們自定義的一個User類:
public class ArrayListTest {
static class User {
private String name;
private int age;
public User(String name , int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) {
ArrayList<Integer> integerArrayList = new ArrayList<>();
int testInt = 10;
integerArrayList.add(testInt);
testInt = 20;
integerArrayList.add(testInt);
System.out.println(integerArrayList.toString());
ArrayList<Double> doubleArrayList = new ArrayList<>();
double testDouble = 100.0;
doubleArrayList.add(testDouble);
testDouble = 200.0;
doubleArrayList.add(testDouble);
System.out.println(doubleArrayList.toString());
ArrayList<User> userArrayList = new ArrayList<>();
User testUser = new User("張三",20);
userArrayList.add(testUser);
testUser.age = 22;
userArrayList.add(testUser);
for (User user : userArrayList) {
System.out.println(user.toString());
}
}
這里我們試了Integer和Double兩種掸宛,看一下結果:
由此可見,修改int和double不會對之前的內容造成影響招拙,但是修改User會對之前的內容造成影響唧瘾。所以措译,ArrayList中如果是基本數(shù)據(jù)類型,那么存放的就是值饰序,如果是對象领虹,那么存放的就是對象的引用而不是對象的拷貝∏笤ィ看樣子這個結論是正確的塌衰,但是需要注意的一個問題就是ArrayList可以存放基本數(shù)據(jù)類型嗎?如果將泛型指定為基本數(shù)據(jù)類型就會報錯:
所以說蝠嘉,ArrayList不存在存放的是基本數(shù)據(jù)類型的問題最疆,只能存放基本數(shù)據(jù)類型的包裝類,也就是說存放基本數(shù)據(jù)類型的時候會自動裝箱成一個對象蚤告,然后把引用存放進去努酸。那好,就算基本數(shù)據(jù)類型存不了杜恰,存的是包裝類获诈,那么我修改了里面的內容之后,Integer和Double兩次卻存放了不同的值心褐,而User修改后存放了兩個相同的內容舔涎。因為如果泛型是Integer或Double的話,兩次存放的是不同的對象的引用而不是一個逗爹,如果是一個的話亡嫌,那當然會導致之前的內容改變咯【蚨《Java編程思想》里面說過:無論何時昼伴,對同一個對象調用hashCode()都應該生成同樣的值。所以我們來打印一下他們的hash值就可以判斷是不是同一個對象镣屹。我們使用System.identityHashCode()來獲取hash值:
……
public static void main(String[] args) {
ArrayList<Integer> integerArrayList = new ArrayList<>();
int testInt = 10;
integerArrayList.add(testInt);
testInt = 20;
integerArrayList.add(testInt);
for (Integer integer : integerArrayList) {
System.out.println(integer + " : "+ System.identityHashCode(integer));
}
System.out.println();
ArrayList<Double> doubleArrayList = new ArrayList<>();
double testDouble = 100.0;
doubleArrayList.add(testDouble);
testDouble = 200.0;
doubleArrayList.add(testDouble);
for (Double aDouble : doubleArrayList) {
System.out.println(aDouble + " : "+ System.identityHashCode(aDouble));
}
ArrayList<User> userArrayList = new ArrayList<>();
User testUser = new User("張三",20);
System.out.println("\nUser修改前:");
System.out.println(testUser.toString());
System.out.println(System.identityHashCode(testUser));
userArrayList.add(testUser);
testUser.age = 22;
System.out.println("User修改后:");
System.out.println(testUser.toString());
System.out.println(System.identityHashCode(testUser));
userArrayList.add(testUser);
System.out.println("\n遍歷ArrayList<User>:");
for (User user : userArrayList) {
System.out.println(user.toString());
System.out.println(System.identityHashCode(user));
}
}
……
打印的結果如下:
從打印的結果可以看出,Integer和Double并不是修改內容价涝,而是存了一個新的對象的引用進去女蜈,所以存放基本數(shù)據(jù)類型的包裝類也是引用并非是值,而User對象修改后卻可以影響到之前已經(jīng)存儲的內容色瘩,兩個User是同一個伪窖。
為什么Integer和Double修改不了呢?因為他們都屬于不可變量居兆,都是final修飾的覆山,也就是說第二次賦值的時候指向的是一個新的對象,而不是修改之前的內容泥栖。從源碼中我們可以看到:
可以看到簇宽,基本數(shù)據(jù)類型的包裝類和String一樣都是final修飾的勋篓,而且Double和Integer等基本數(shù)據(jù)類型包裝類中也沒有提供修改值的方法,也就是說之前看樣子是在修改數(shù)據(jù)魏割,其實是指向了一個新的內存地址譬嚣,ArrayList中第二次存放數(shù)據(jù)的時候,并沒有改變第一次存放的引用中的內存地址中的值钞它,而是存了一個新的引用拜银。
結論
那么到現(xiàn)在為止,就可以得出一個結論了: ArrayList中存放的是引用而不是值
遭垛。
網(wǎng)上有的人說“存基本數(shù)據(jù)類型存的是值尼桶,存對象存的是引用”這個結論是錯誤的【庖牵回到最開始的問題泵督,我這題應該怎么寫?其實只要克隆一份就好了:
……
if (residual == 0) {
result.add((List<Integer>) solution.clone());
return;
}
……