一個有趣且令人困惑的代碼片段
Code A:
final ConcurrentHashMap<String, Ref> REFS_MAPS = new ConcurrentHashMap<String, Ref>();
public void put(String key) {
Ref ref = new Ref(key, "1");
ref = new Ref(key, "2");
REFS_MAPS.put(key, ref);
}
public Ref get(String key) {
return REFS_MAPS.get(key);
}
它有可能會得到"1"嗎?
錯誤的解釋
在多線程調度的情況下,相同的 key 多次同時調用 put 和 get 方法,從 REFS_MAPS 方法 get 時,正好 put 運行到 Ref ref = new Ref(key, "1")
,所以就得到了“1”的值,如下所示:
image
這個解釋是錯誤的,不會得到“1”。
REFS_MAPS
的 hash Node 存儲指向 “ref” 對象的值,而不是對象引用。因此,當 ref 在 put()
方法時,ref 的 val 先指向堆中的“1”,後指向堆中的“2”,如下所示:
image
常見的困惑問題
將 put 方法改一下:
Code B:
public void put(String key) {
Ref ref = new Ref(key, "1");
REFS_MAPS.put(key, ref);
ref = new Ref(key, "2");
}
它有可能會得到"1"嗎?
一定會是“1”,雖然 ref 的指向堆中的“2”,但是 REFS_MAPS 的 hash Node 存儲指向 “ref” 對象的值還是“1”。
image
再將 put 方法改一下:
Code C:
public void put(String key) {
Ref ref = new Ref(key, "1");
REFS_MAPS.put(key, ref);
ref.setValue("2");
}
它有可能會得到"1"嗎?
不會的得到“1”,因爲 ref 和 REFS_MAPS
存儲的 “ref” 對象只指向的同一個值,當 ref 修改了值,REFS_MAPS 中 ref 的值也被修改了。
image
代碼背後真正的意義是什麼?
我們知道,值傳遞(pass by value)是指在調用函數時將實際參數複製一份傳遞到函數中,引用傳遞(pass by reference)是指在調用函數時將實際參數的地址直接傳遞到函數中,而 Java 只有值傳遞。
在 Code B 中,ref = new Ref(key, "2")
會重新開闢一片內存空間,賦值給 ref,後面的任何修改都不會改變 Ref ref = new Ref(key, "1")
的內容,這裏不是引用傳遞,如果是引用傳遞的話,REFS_MAPS
中的引用也應該會改變,但是實際上並沒有。
在 Code C 中,ref.setValue("2")
影響了 REFS_MAPS 中的值,因爲這裏是把 ref 的引用的地址複製了一份,傳遞給了 REFS_MAPS
。所以,ref 其實是值傳遞,把 ref 對象引用的地址當做值傳遞給了 REFS_MAPS
。
所以,值傳遞和引用傳遞的區別並不是傳遞的內容。而是實參到底有沒有被複制一份給形參。