閱讀832 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Java值傳遞和引用傳遞

兩個看似沒什麼區別的代碼

代碼一

public static void main(String[] args) {
    List<Object> objects = null;
    foo(objects);
}

public static void foo(List<Object> objectsCopy) {
    objectsCopy = new ArrayList<>();
    objectsCopy.add(new Object());
    // Bla bla bla
}

代碼二

public static void main(String[] args) {
    List<Object> objects = new ArrayList<>();
    foo(objects);
}

public static void foo(List<Object> objectsCopy) {
    objectsCopy.add(new Object());
    // Bla bla bla
}

真的沒有區別嗎?

以上是我最近寫代碼時遇到的,當我使用第一種寫法的時候,我發現objects 一直是null,略微詫異了一會,我換了第二種寫法,問題解決。

老司機可能一看就知道了,這是一個值傳遞和引用傳遞的經典問題。

那麼為什麼第一個不是引用傳遞?難道List不是引用類型嗎?一圖勝過千言萬語,先來張圖解釋一下。

這裏寫圖片描述

引用本身以及基本數據類型是存放在棧裏的,而引用類型所指向的內容存放在堆內。據此,畫出了上圖所示內容,objects表示引用本身(堆中的地址,後文地址均表示此意),而content表示引用指向的具體內容(後文也均使用該詞表示引用指向的具體內容)。
解釋:
代碼一中,引用objects,其值為null,所以沒有指向任何堆內存;
當我們調用foo方法時,引用objects首先會在foo 方法中被拷貝一份副本objectsCopyobjectsCopy同樣也不指向任何堆內容(在foo方法中,所有的操作都是通過objects 的副本來操作的);
foo 方法中objectsCopy = new ArrayList<>(); 語句被調用時,content的地址&content(&在C/C++中表示取地址)就會被寫到棧objectsCopy,此時objectsCopy 就指向了content;
可以很清楚的看到,接下來所有的操作都會改變content的內容,但是很遺憾,objects不會有任何改變,始終為null。

這裏寫圖片描述

代碼二中,main函數中執行了語句objects = new ArrayList<>();,於是引用objects指向堆中的content
當我們調用foo方法時,引用objects首先會在foo 方法中被拷貝一份副本objectsCopy ,因為objects指向堆中的content,於是objectsCopy指向堆中的content
接下來對objectsCopy 的所有的操作都會改變content的內容,因為objects指向的也是content,所以就改變了objects指向的內容。***這就是著名的引用傳遞。***

那麼代碼一是什麼傳遞?
函數中修改一個存放在棧中的數據,而傳遞進來的參數是它本身,這是什麼傳遞?或者說函數傳了一個引用參數(地址),而現在修改的是引用本身,這是什麼傳遞?
這就是地地道道的、徹頭徹尾的goddamn值傳遞。

僅看表麵上傳遞的是引用類型還是值類型是無法判斷這將是值傳遞還是引用傳遞,這要取決於你具體的操作是改變引用本身(地址)還是引用指向的內容(content)。
盡管在代碼一中,我傳遞的是一個引用類型,但是我修改的是引用本身,所以它是值傳遞;代碼二中我修改的是content,但我傳遞的是content的引用,所以它才是引用傳遞。
因此,修改A,傳遞A的引用,這就是引用傳遞;修改A,傳遞A,這就是值傳遞。傳遞引用類型不是引用傳遞。

好繞啊。。。C++或許更好理解一些。

修改引用指向的內容(x和y),傳遞的是地址(指針)int* xint* y(地址可以對應理解為Java中的引用),這就是引用傳遞。

void swap(int* x, int* y)
{
    int temp = x;
    x = y;
    y = temp;
}

修改指針,傳遞的也是指針本身,這就是值傳遞。

void foo(int* x, int* y)
{
    x = new int(2);
    y = new int(5);
}

盡管我自認為在C/C++中就已經將這兩種傳遞理解得很透徹了,但是不經意間這錯誤還是犯得徹徹底底。為此,我總結出這樣的一句話:

如果你想修改引用指向的內容,你需要傳遞引用;如果你想要修改引用本身的值,那麼你需要傳遞引用的引用,否則那隻是穿上了引用外衣的值傳遞。

最後再補充一點C++中值傳遞和引用傳遞。

值傳遞

void swap(int x, int y)
{
    int temp = x;
    x = y;
    y = temp;
}

地址傳遞

void swap(int* x, int* y)
{
    int temp = x;
    x = y;
    y = temp;
}

引用傳遞

void swap(int &x, int &y)
{
    int temp = x;
    x = y;
    y = temp;
}

除值傳遞外,地址和引用傳遞都會改變x和y的值。

最後更新:2017-07-03 22:02:12

  上一篇:go  阿裏雲首席架構師唐洪:擁抱開源的雲端更具生命力
  下一篇:go  優惠券!阿裏雲幸運券免費領取,幫助站長降低建站服務器成本