一個對象引用的思考

一個有趣且令人困惑的代碼片段

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

所以,值傳遞和引用傳遞的區別並不是傳遞的內容。而是實參到底有沒有被複制一份給形參。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章