Java引數是傳值還是傳引用
前言
對於Java引數是傳值還是傳引用這個問題,大家總是眾說紛紜,在《Thinking in Java》中是這麼解釋的:When you’re passing primitives into a method, you get a distinct copy of the primitive. When you’re passing a reference into a method, you get a copy of the reference.(對於基本型別變數,Java是傳值的副本;對於所有的引用型別變數,如String等,Java都是傳引用的副本)這裡我們首先需要明確下什麼是基本型別變數,什麼是引用型別變數。
- 基本型別變數
- 整型:byte,short,int,long
- 浮點型:float,double
- 字元型:char
- 布林型:boolean
- 引用型別變數
- 陣列
- 類
- 介面
程式碼說明
接下來將三個程式碼示例對Java引數是傳值還是傳引用進行詳細的說明。
示例程式碼1:
public class Test1 { public static void main(String[] args) { int a = 1; System.out.println("Before test(int) : a = " + a); test(a); System.out.println("After test(int) : a = " + a); } public static void test(int a) { a++; System.out.println("In test(int) : a = " + a); } }
執行結果 :
Before test(int) : a = 1
In test(int) : a = 2
After test(int) : a = 1
不難看出,雖然在test(int)方法中改變了傳進來的引數值,但這個對引數的源變數本身(即對main(String[])方法中的a變數)並沒有影響,說明在傳遞基本型別變數時,實際上是將引數值作為一個副本傳進方法函式的,在呼叫的方法函式中,不管怎麼改變這個值,其結果都只是改變了副本的值,而不會影響到源值。
流程:
- 主函式進棧,a初始化。
- 呼叫test(int)方法,test(int)方法進棧,將main(String[])中a的值
- test(int)方法中對a的值進行a++自增運算。
- test(int)方法執行完畢,a的值加1。
- test(int)方法彈棧。
- 主函式彈棧。
示例程式碼2:
public class Test2 {
public static void main(String[] args) {
StringBuffer string = new StringBuffer("Hello");
System.out.println("Before test(StringBuffer) : string = " + string);
test(string);
System.out.println("After test(StringBuffer) : string = " + string);
}
public static void test(StringBuffer str) {
str.append(", world!");
System.out.println("In test(StringBuffer) : str = " + str);
}
}
執行結果:
Before test(StringBuffer) : string = Hello
In test(StringBuffer) : str = Hello, world!
After test(StringBuffer) : string = Hello, world!
在main(String[])中呼叫了test(StringBuffer)方法,並將string作為引數進行傳遞。這裡的string是一個物件引用,Java實際上將它的副本傳進方法函式的,這個函式裡面的引用副本指向的是物件的地址,通過引用副本找到地址並修改地址中的值,也就改變了物件。
流程:
- 主函式進棧,StringBuffer string初始化。
- 呼叫test方法,test( )進棧,將string指向的地址值,複製一份給str。
- test方法中,根據地址值,找到堆中的StringBuffer物件,並將它的值改為“Hello,world!”。
- test方法執行完畢,StringBuffer的值已經改變。
- test方法彈棧。
- 主函式彈棧。
示例程式碼3:
public class Test3 {
public static void main(String[] args) {
String string = "Hello";
System.out.println("Before test(StringBuffer) : string = " + string);
test(string);
System.out.println("After test(StringBuffer) : string = " + string);
}
public static void test(String str) {
str = "Hello, world!";
System.out.println("In test(StringBuffer) : str = " + str);
}
}
執行結果:
Before test(StringBuffer) : string = Hello
In test(StringBuffer) : str = Hello, world!
After test(StringBuffer) : string = Hello
也許大家會覺得奇怪,類比示例程式碼2,同樣是將引用型別作為引數傳進方法函式中,為什麼示例程式碼2能夠改變物件的值,而示例程式碼3中卻不行?首先,我們需要清楚的一點,在String的API中有這麼一句話:their values cannot be changed after they are created,即String類是final型別的,String的值在建立之後不能被更改。所以在執行str = "Hello, world!";時,其過程為:首先系統會自動生成一個新的String物件,並把這個新物件的值設為“Hello,world!”,然後把這個物件的引用賦給str。既然物件都是新的,那麼就與原來的“Hello”沒有任何關係。因此,當函式結束,引用副本str的作用消失,原來記憶體地址上的內容並沒有任何的改變,列印結果仍是Hello。而在示例程式碼2中,str.append(", world!");就不同了,StringBuffer是產生一塊記憶體空間,相關的增刪改都在其中執行,所以新增一句“,world!”仍然是在同一塊記憶體地址上進行,str所指向的引用並沒有改變。
流程:
- 主函式進棧,string初始化。
- 呼叫test方法,test( )進棧,將string指向的地址值,複製一份給str。
- test方法中,重現建立了一個String物件”Hello,world!”,並將str指向了新的地址值。
- test方法執行完畢,str所指向的地址值已經改變,但string指向的地址值不變。
- test方法彈棧。
- 主函式彈棧。
小結
對於Java引數是傳值還是傳引用這個問題,對於基本型別變數,Java是傳值的副本;對於引用型別變數,即所有的物件型變數,Java都是傳引用的副本。
PS:上述如果有任何理解錯誤的地方,歡迎大家批評指出。