關於這個問題,引起過不少普遍的討論,看來不少程序員對於這個問題的理解都不盡相同,甚至不少人理解的是錯誤的。還有的人可能知道Java中的參數傳遞是值傳遞,可是說不出來爲何。程序員
在開始深刻講解以前,有必要糾正一下你們之前的那些錯誤見解了。若是你有如下想法,那麼你有必要好好閱讀本文。函數
錯誤理解一:值傳遞和引用傳遞,區分的條件是傳遞的內容,若是是個值,就是值傳遞。若是是個引用,就是引用傳遞。
錯誤理解二:Java是引用傳遞。
錯誤理解三:傳遞的參數若是是普通類型,那就是值傳遞,若是是對象,那就是引用傳遞。lua
咱們都知道,在Java中定義方法的時候是能夠定義參數的。好比Java中的main方法,public static void main(String[] args),這裏面的args就是參數。參數在程序語言中分爲形式參數和實際參數。spa
形式參數:是在定義函數名和函數體的時候使用的參數,目的是用來接收調用該函數時傳入的參數。
實際參數:在調用有參函數時,主調函數和被調函數之間有數據傳遞關係。在主調函數中調用一個函數時,函數名後面括號中的參數稱爲「實際參數」。3d
簡單舉個例子:code
public static void main(String[] args) {
ParamTest pt = new ParamTest();
pt.sout("Hollis");//實際參數爲 Hollis
}
public void sout(String name) { //形式參數爲 name
System.out.println(name);
}
實際參數是調用有參方法的時候真正傳遞的內容,而形式參數是用於接收實參內容的參數。對象
上面提到了,當咱們調用一個有參函數的時候,會把實際參數傳遞給形式參數。可是,在程序語言中,這個傳遞過程當中傳遞的兩種狀況,即值傳遞和引用傳遞。咱們來看下程序語言中是如何定義和區分值傳遞和引用傳遞的。blog
值傳遞(pass by value)是指在調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中若是對參數進行修改,將不會影響到實際參數。
引用傳遞(pass by reference)是指在調用函數時將實際參數的地址直接傳遞到函數中,那麼在函數中對參數所進行的修改,將影響到實際參數。內存
有了上面的概念,而後你們就能夠寫代碼實踐了,來看看Java中究竟是值傳遞仍是引用傳遞 ,因而,最簡單的一段代碼出來了:字符串
public static void main(String[] args) {
ParamTest pt = new ParamTest();
int i = 10;
pt.pass(10);
System.out.println("print in main , i is " + i);
}
public void pass(int j) {
j = 20;
System.out.println("print in pass , j is " + j);
}
上面的代碼中,咱們在pass方法中修改了參數j的值,而後分別在pass方法和main方法中打印參數的值。輸出結果以下:
print in pass , j is 20
print in main , i is 10
可見,pass方法內部對name的值的修改並無改變實際參數i的值。那麼,按照上面的定義,有人獲得結論:Java的方法傳遞是值傳遞。
可是,很快就有人提出質疑了(哈哈,因此,不要輕易下結論咯。)。而後,他們會搬出如下代碼:
public static void main(String[] args) {
ParamTest pt = new ParamTest();
User hollis = new User();
hollis.setName("Hollis");
hollis.setGender("Male");
pt.pass(hollis);
System.out.println("print in main , user is " + hollis);
}
public void pass(User user) {
user.setName("hollischuang");
System.out.println("print in pass , user is " + user);
}
一樣是一個pass方法,一樣是在pass方法內修改參數的值。輸出結果以下:
print in pass , user is User{name='hollischuang', gender='Male'}
print in main , user is User{name='hollischuang', gender='Male'}
通過pass方法執行後,實參的值居然被改變了,那按照上面的引用傳遞的定義,實際參數的值被改變了,這不就是引用傳遞了麼。因而,根據上面的兩段代碼,有人得出一個新的結論:Java的方法中,在傳遞普通類型的時候是值傳遞,在傳遞對象類型的時候是引用傳遞。
可是,這種表述仍然是錯誤的。不信你看下面這個參數類型爲對象的參數傳遞:
public static void main(String[] args) {
ParamTest pt = new ParamTest();
String name = "Hollis";
pt.pass(name);
System.out.println("print in main , name is " + name);
}
public void pass(String name) {
name = "hollischuang";
System.out.println("print in pass , name is " + name);
}
上面的代碼輸出結果爲
print in pass , name is hollischuang
print in main , name is Hollis
這又做何解釋呢?一樣傳遞了一個對象,可是原始參數的值並無被修改,難道傳遞對象又變成值傳遞了?
上面,咱們舉了三個例子,表現的結果卻不同,這也是致使不少初學者,甚至不少高級程序員對於Java的傳遞類型有困惑的緣由。
其實,我想告訴你們的是,上面的概念沒有錯,只是代碼的例子有問題。來,我再來給你們畫一下概念中的重點,而後再舉幾個真正恰當的例子。
值傳遞(pass by value)是指在調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中若是對參數進行修改,將不會影響到實際參數。
引用傳遞(pass by reference)是指在調用函數時將實際參數的地址直接傳遞到函數中,那麼在函數中對參數所進行的修改,將影響到實際參數。
那麼,我來給你們總結一下,值傳遞和引用傳遞以前的區別的重點是什麼。
咱們上面看過的幾個pass的例子中,都只關注了實際參數內容是否有改變。如傳遞的是User對象,咱們試着改變他的name屬性的值,而後檢查是否有改變。其實,在實驗方法上就錯了,固然獲得的結論也就有問題了。
爲何說實驗方法錯了呢?這裏咱們來舉一個形象的例子。再來深刻理解一下值傳遞和引用傳遞,而後你就知道爲啥錯了。
你有一把鑰匙,當你的朋友想要去你家的時候,若是你直接把你的鑰匙給他了,這就是引用傳遞。這種狀況下,若是他對這把鑰匙作了什麼事情,好比他在鑰匙上刻下了本身名字,那麼這把鑰匙還給你的時候,你本身的鑰匙上也會多出他刻的名字。
你有一把鑰匙,當你的朋友想要去你家的時候,你復刻了一把新鑰匙給他,本身的還在本身手裏,這就是值傳遞。這種狀況下,他對這把鑰匙作什麼都不會影響你手裏的這把鑰匙。
可是,無論上面那種狀況,你的朋友拿着你給他的鑰匙,進到你的家裏,把你家的電視砸了。那你說你會不會受到影響?而咱們在pass方法中,改變user對象的name屬性的值的時候,不就是在「砸電視」麼。
還拿上面的一個例子來舉例,咱們真正的改變參數,看看會發生什麼?
public static void main(String[] args) {
ParamTest pt = new ParamTest();
User hollis = new User();
hollis.setName("Hollis");
hollis.setGender("Male");
pt.pass(hollis);
System.out.println("print in main , user is " + hollis);
}
public void pass(User user) {
user = new User();
user.setName("hollischuang");
user.setGender("Male");
System.out.println("print in pass , user is " + user);
}
上面的代碼中,咱們在pass方法中,改變了user對象,輸出結果以下:
print in pass , user is User{name='hollischuang', gender='Male'}
print in main , user is User{name='Hollis', gender='Male'}
咱們來畫一張圖,看一下整個過程當中發生了什麼,而後我再告訴你,爲啥Java中只有值傳遞。
稍微解釋下這張圖,當咱們在main中建立一個User對象的時候,在堆中開闢一塊內存,其中保存了name和gender等數據。而後hollis持有該內存的地址0x123456(圖1)。當嘗試調用pass方法,而且hollis做爲實際參數傳遞給形式參數user的時候,會把這個地址0x123456交給user,這時,user也指向了這個地址(圖2)。而後在pass方法內對參數進行修改的時候,即user = new User();,會從新開闢一塊0X456789的內存,賦值給user。後面對user的任何修改都不會改變內存0X123456的內容(圖3)。
上面這種傳遞是什麼傳遞?確定不是引用傳遞,若是是引用傳遞的話,在user=new User()的時候,實際參數的引用也應該改成指向0X456789,可是實際上並無。
經過概念咱們也能知道,這裏是把實際參數的引用的地址複製了一份,傳遞給了形式參數。因此,上面的參數實際上是值傳遞,把實參對象引用的地址當作值傳遞給了形式參數。
咱們再來回顧下以前的那個「砸電視」的例子,看那個例子中的傳遞過程發生了什麼。
一樣的,在參數傳遞的過程當中,實際參數的地址0X1213456被拷貝給了形參,只是,在這個方法中,並無對形參自己進行修改,而是修改的形參持有的地址中存儲的內容。
因此,值傳遞和引用傳遞的區別並非傳遞的內容。而是實參到底有沒有被複制一份給形參。在判斷實參內容有沒有受影響的時候,要看傳的的是什麼,若是你傳遞的是個地址,那麼就看這個地址的變化會不會有影響,而不是看地址指向的對象的變化。就像鑰匙和房子的關係。
那麼,既然這樣,爲啥上面一樣是傳遞對象,傳遞的String對象和User對象的表現結果不同呢?咱們在pass方法中使用name = "hollischuang";試着去更改name的值,陰差陽錯的直接改變了name的引用的地址。由於這段代碼,會new一個String,在把引用交給name,即等價於name = new String("hollischuang");。而原來的那個」Hollis」字符串仍是由實參持有着的,因此,並無修改到實際參數的值。
因此說,Java中其實仍是值傳遞的,只不過對於對象參數,值的內容是對象的引用。
不管是值傳遞仍是引用傳遞,其實都是一種求值策略(Evaluation strategy)。在求值策略中,還有一種叫作按共享傳遞(call by sharing)。其實Java中的參數傳遞嚴格意義上說應該是按共享傳遞。
按共享傳遞,是指在調用函數時,傳遞給函數的是實參的地址的拷貝(若是實參在棧中,則直接拷貝該值)。在函數內部對參數進行操做時,須要先拷貝的地址尋找到具體的值,再進行操做。若是該值在棧中,那麼由於是直接拷貝的值,因此函數內部對參數進行操做不會對外部變量產生影響。若是原來拷貝的是原值在堆中的地址,那麼須要先根據該地址找到堆中對應的位置,再進行操做。由於傳遞的是地址的拷貝因此函數內對值的操做對外部變量是可見的。
簡單點說,Java中的傳遞,是值傳遞,而這個值,其實是對象的引用。
而按共享傳遞其實只是按值傳遞的一個特例罷了。因此咱們能夠說Java的傳遞是按共享傳遞,或者說Java中的傳遞是值傳遞。