java对象引用出错_“Java有值传递和引用传递”为什么错了?
前言
初學Java的時候,老師在課堂上說“Java有值傳遞和引用傳遞”,但網上“Java只有值傳遞”的呼聲很高。
本人在查找資料的過程中,在這兩個說法之間反復橫跳。經過本人的整理后,其實還真的是Java只有值傳遞。
什么是值傳遞?什么是引用傳遞?
首先,我們先明確一下值傳遞和引用傳遞的定義(來自維基百科)。
值傳遞
When a parameter is passed by value, the caller and callee have two independent variables with the same value. If the callee modifies the parameter variable, the effect is not visible to the caller.
拙譯:當一個參數進行值傳遞的時候,調用者和被調用者有兩個值相同的獨立變量。如果被調用者修改了參數值,并不會影響調用者。
引用傳遞
When a parameter is passed by reference, the caller and the callee use the same variable for the parameter. If the callee modifies the parameter variable, the effect is visible to the caller’s variable.
拙譯:當一個參數進行引用傳遞的時候,調用者和被調用者使用同一個變量。如果被調用者修改了參數值,調用者也會受到影響。
總結一下,值傳遞和引用傳遞最本質的區別在與,調用者和被調用者有沒有使用同一個變量。
可能有人還是對這兩個定義抱有疑問,沒關系,我們用C++做個例子。
C++中的值傳遞和引用傳遞(不感興趣可略過)
這里先說明下為什么用C++。因為C++的有指針概念,所以對于指針和引用是有做嚴格區分的,感興趣的小伙伴可以看下這篇博客:C/C++中的值傳遞,引用傳遞,指針傳遞,指針引用傳遞。
基于需要,本文對C++的值傳遞和引用傳遞來進行簡要說明。
C++的值傳遞
可以看到,a變量(地址為0x22fe4c)在調用f()函數,進行值傳遞的變量p(地址為0x22fe20)已經是另一個變量了,而且在改變p的值后,地址為0x22fe4c的內容并沒有改變,即a的值沒有改變。所以值傳遞無法改變傳遞的變量的值。
引用傳遞
可以看到,a變量(地址為0x22fe4c)在調用f()函數,進行值傳遞的變量p(地址為0x22fe4c)還是同一個變量,而且在改變p的值后,地址為0x22fe4c的內容變為0xff,即a的值發生改變。所以引用傳遞可以改變傳遞的變量的值。
總結一下,在C++中的值傳遞和引用傳遞在大體上是跟值傳遞和引用傳遞的定義相符的。也就是說這個定義是可以在編程語言中適用的。
Java中的值傳遞和引用傳遞
說明一下,System.identityHashCode()的作用是用來判斷兩個對象是否是內存中同一個對象,跟用==判斷內存地址是否一樣的效果一樣,有疑惑的朋友可以看下這篇博客:Java:Object.hashCode()和System.identityHashCode()的區別
值傳遞
以上可以得到跟值傳遞定義一樣的結論,Java的值傳遞過程中,會復制傳遞的參數值到另一個變量,這兩個變量之間互不影響,而且只有基本數據類型進行傳遞時是以值傳遞的方式。
引用傳遞
從這個圖也可以看出,Java引用傳遞過程中的兩個數組a, b是指向同一個內存地址,這一個變量的改變會影響到另一個變量,而且只有除了String類型以外的其他對象類型在作為參數傳遞時,是使用引用傳遞的。
從這兩個例子來看,Java既有值傳遞也有引用傳遞啊,所以“Java只有值傳遞”這個說法是錯誤的?非也。
String類型?
在查找資料的過程中,很多人的說法是“String類型也是值傳遞”,為什么呢?舉個栗子:
public class Test {
public static void main(String[] args){
Test t = new Test();
String s = "oh";
t.test(s);
System.out.println("print in main , ans is " + s);
}
public void test(String s) {
s = "hello";
System.out.println("print in test , ans is " + s);
}
}
運行的結果是這樣的:
print in test , ans is hello
print in main , ans is oh
我們可以看到實參s在傳入方法test()后,形參s’改變了也不影響實參的值。為什么?(以下解釋基于《深入理解Java虛擬機》的理解)
在字符串s傳遞過程中:
虛擬機在常量池劃出一塊內存,其地址為addr1,存值“oh”;
虛擬機在棧中分配s一塊內存,內存中存的值為addr1;
虛擬機將s復制一份出來,即s‘,s和s’內存不同,但是它們的值都是addr1;
將s’傳入方法體;
方法體在常量池中劃分一塊內存,其地址為addr2,存值"hello";
方法體將s’值改為addr2;
方法結束,main打印的是s,因為s存的值為addr1,所以打印出來的結果為addr1存的值:“oh”
所以String類型在調用過程中也是采用值傳遞。
“Java只有值傳遞”是錯誤的嗎?
不是,我們拿“Java引用傳遞”的例子來解釋。
public static void main(String[] args) {
int[] a = {1, 2, 3};
f(a);
...
}
public static void f(int[] b) {
b[0] = 100;
...
}
我們在得到結論的時候,是因為:在經過方法f()之后,a[0]的值從1改變為100。可這個過程嚴格上是引用傳遞嗎?我們從虛擬機內部來觀察傳遞過程:
虛擬機在堆上劃分了一塊用于存儲數組a的內存,其內存地址為addr1。
虛擬機在棧中分配給a一個內存地址,這個地址存的是addr1。
虛擬機復制a,其別名為b,a和b的內存地址不同,但是他們的值都是addr1。
將b傳入方法,方法改變了addr1的數組的值。
方法結束,f和main打印的都是addr1的內存值,都是同一個對象。
在這個過程中,a和b的內存地址不同,但是他們存值的內存地址后的對象是同一個。就是下圖的這種關系:
所以,從虛擬機的角度來看,實參a和形參b是兩個獨立變量,只是實參a把對象地址當做值傳遞給形參b。按照值傳遞的定義來看,a和b只是兩個值相同的獨立變量,Java是值傳遞。而a和b的值的值(這里不是寫錯)所指向的內容是同一個,所以我們前面在看到是“引用傳遞”的情況。
總結
嚴格來說,Java只有值傳遞,因為在實參傳遞的過程中,虛擬機復制了實參的值到形參,并且實參和形參指向的不是同一塊內存。這個說法,是基于這種邏輯(a、b、c這三個變量是獨立的,b為對象地址):
這時,我們只要保證實參和形參是兩個獨立的個體,且值都是b就好。
而"Java有值傳遞和引用傳遞"這一說法的出現,是因為我們在剛學習Java的時候還不到了解虛擬機的水平,沒有了解到,實參和形參是兩個值相同的不同獨立體,利用這種“美好的”誤會來理解“引用傳遞”吧。
參考資料:
以及正文中提及的文章
總結
以上是生活随笔為你收集整理的java对象引用出错_“Java有值传递和引用传递”为什么错了?的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: java怎么实现同步到微博功能_新浪微博
 - 下一篇: lasso特征选择python_转:结合