Java 值傳遞 or 引用傳遞?
Java 方法傳參 值傳遞 or 引用傳遞?
結論:Java採用的是值傳遞
先建立一些基礎的概念
什麼是值傳遞和引用傳遞?
- 值傳遞(pass by value):是指在調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中如果對參數進行修改,將不會影響到實際參數
- 引用傳遞(pass by reference):是指在調用函數時將實際參數的地址直接傳遞到函數中,那麼在函數中對參數所進行的修改,將影響到實際參數
Java的數據類型分為兩類
- 基本類型(int float等)
- 引用類型(string, 數組等, 以及一切類對象)
如下圖,展示了兩者在記憶體中存儲形式,基本類型存儲的是值,而引用類型存儲的是地址,該地址指向值所在的記憶體空間。引用類型有點類似於C語言中的指針。
實踐出真知,本文做了三個實驗來論證為什麼Java是值傳遞。
實驗一:證明基本類型是值傳遞
先上程式碼
public class test {
public static void main(String[] args) {
int a = 1000;
changeInt(a);
System.out.println("(main)a = " + a);
}
private static void changeInt(int a) {
a = 3;
System.out.println("(changeInt)a = " + a);
}
}
// 運行程式碼會得到如下結果
// (changeInt)a = 3
// (main)a = 1000
在程式碼中,我們向changInt
方法傳入了變數a
,並在方法內部中改變了a
的值,但主程式中a的值並沒有改變。因此基本類型是值傳遞。
實驗二:引用類型是引用傳遞?
先上程式碼
public class test {
public static void main(String[] args) {
Student std = new Student();
std.name = "Nick";
changeStd(std);
System.out.println("(main)name = " + std.name);
}
private static void changeStd(Student std) {
std.name = "Paul";
System.out.println("(change)name = " + std.name);
}
}
class Student {
String name;
}
// 運行結果
// (change)name = Paul
// (main)name = Paul
在上段程式碼中,我們向changeStd
方法傳入了一個student
類實例,並在方法內部中改變了學生類實例中的name
欄位。從運行的結果我們可以看出主程式中的學生實例的姓名也被改變。難道引用類型採用的是引用傳遞?當然不是,接下來繼續看第三個實驗。
實驗三:引用類型是值傳遞?
public class test {
public static void main(String[] args) {
Student std = new Student();
std.name = "Nick";
changeStd(std);
System.out.println("(main)name = " + std.name);
}
private static void changeStd(Student std) {
std = new Student();
std.name = "Paul";
System.out.println("(change)name = " + std.name);
}
}
class Student {
String name;
}
// 運行結果
// (change)name = Paul
// (main)name = Nick
我們改變了方法內部的賦值,我們先重新給std
創建了一個新的學生實例,並將名字修改。其結果與實驗二相反,方法內部的賦值操作並未改變。難道引用類型又是值傳遞?
總結
要想理解三個實驗的運行結果,其實原理並不複雜。
實驗一:下圖表示的是,兩個變數的記憶體情況。只有可能是兩個a
有著不同的地址,方法內部的賦值才不會改變主程式的a值。如果兩者是同一記憶體空間,那麼方法內部的修改,必定會影響主程式的a值。
實驗二:下圖表示的是方法內部還未賦值的時候,兩個變數的記憶體情況。兩個變數雖然有著不同的記憶體空間,但是存儲都是Nick的地址,實際指向的是同一個地址空間。有了Nick的存儲地址,當然可以方法內部去改變Nick的值。
賦值後
實驗三:與實驗二相同,在還未創建新的實例時,兩者指向的都是Nick。但是在給方法內部的std
賦值之後,實際上改變了其存放的地址,將其指向了一個新的對象Paul。
根據三個實驗的結果,我們可以論證出Java採用的是值傳遞,只不過對於基本類型而言,傳遞的是一個具體的值,而對於引用類型而言傳遞的也是一個具體的值,只不過這個值是一個地址。而有這個地址,我們可以對地址指向的地址空間進行操作,所以會出現實驗二的情況,但是如果我們對值本身進行改變賦值,兩者是互不影響的。
看到一個例子說的很形象: