概述 java中的參數傳遞問題可以根據參數的類型大致可以分為三類:傳遞基本類型,傳遞String類型,傳遞引用類型,至於最終是否可以歸納為值傳遞和引用傳遞,根據每個人的理解不同,答案不同,此處不做強調。 傳遞基本類型 結果 :Before ...
概述
java中的參數傳遞問題可以根據參數的類型大致可以分為三類:傳遞基本類型,傳遞String類型,傳遞引用類型,至於最終是否可以歸納為值傳遞和引用傳遞,根據每個人的理解不同,答案不同,此處不做強調。
傳遞基本類型
public class Test1 {
public static void main(String[] args) {
int n = 3;
System.out.println("Before change, n = " + n);
changeData(n);
System.out.println("After changeData(n), n = " + n);
}
public static void changeData(int n) {
n = 10;
}
}
結果:Before change, n = 3
After changeData(n), n = 3
解析(比較簡單不結合位元組碼分析):
1.線程調用main方法,創建棧幀A,局部變數表有n=3
2.main方法中調用changeDate方法,傳入參數n=3,線程創建棧幀B,將10賦給n後,局部變數表有n=10
3.changeDate方法執行完畢,棧幀B彈出,輸出棧幀A中n的值為3
傳遞String類型
public class Test2 {
public static void main(String[] args) {
String str = new String("String");
System.out.println("Before change, str = " + str);
changeData(str);
System.out.println("After changeData(n), str = " + str);
}
public static void changeData(String str) {
str = "newString";
}
}
結果:Before change, str = String
After changeData(n), str = String
指令碼為(將上述代碼兩條輸出語句刪除後進行編譯,反彙編,為了突出主要過程):
public static void main(java.lang.String[]);
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // 返回常量池中字元串的引用,並且入棧
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
10: aload_1
11: invokestatic #5 // Method changeData:(Ljava/lang/String;)V
14: return
public static void changeData(java.lang.String);
0: ldc #6 // 返回常量池中字元串的引用,並且入棧
2: astore_0
3: return
}
解析: 1.new,dup,Idc,invokespecial,astore_1:在棧幀A中完成了實例化一個String對象,並將一個指向該對象的引用存入了局部變數表的操作
2.aload_1,invokestatic:調用changeDate方法,傳入引用,創建棧幀B
3.Idc,astore_0,return:在棧幀B中完成了將指向常量池中"newString"字元串的引用壓入操作數棧並且將該引用存入局部變數表的操作,之後棧幀B彈出
4.棧幀A局部變數表中那個引用依然指向String對象,其值依然為String
傳遞引用類型
public class Test3 {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello ");
System.out.println("Before change, sb = " + sb);
changeData(sb);
System.out.println("After changeData(n), sb = " + sb);
}
public static void changeData(StringBuffer strBuf) {
strBuf.append("World!");
}
}
結果:Before change, sb = Hello
After changeData(n), sb = Hello World!
指令碼為(將上述代碼兩條輸出語句刪除後進行編譯,反彙編,為了突出主要過程):
public static void main(java.lang.String[]);
0: new #2 // class java/lang/StringBuffer
3: dup
4: ldc #3 // String Hello
6: invokespecial #4 // Method java/lang/StringBuffer."<init>":(Ljava/lang/String;)V
9: astore_1
10: aload_1
11: invokestatic #5 // Method changeData:(Ljava/lang/StringBuffer;)V
14: return
public static void changeData(java.lang.StringBuffer);
stack=2, locals=1, args_size=1
0: aload_0
1: ldc #6 // String World!
3: invokevirtual #7 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/ StringBuffer;
6: pop
7: return
}
解析(說明下和String傳參區別的地方):
在changeDate方法中有了aload操作,也就是將傳遞來的引用壓入了操作數棧,並且之後的Idc,invokevirtual操作說明對該引用指向的對象進行了相關操作,很顯然在棧幀B彈出時,棧幀A局部變數表中的引用指向的對象發生了變化。
總結
回頭看一下:綜合來看基本變數和String變數傳參,對傳入參數進行改變的時候,都沒有用到傳入的參數值(也就是沒有aload操作),直接將基本類型值或者常量池中字面量引用賦值給變數。怎麼看都有些彆扭,因為String本質上是一個類和基本類型中終究是不同的,我的理解是:String類既然設計成final類,暗示string變數的復用帶來的正面效果大於由於不能改變String變數而必須存入一個新的string字元串的負面效果,那麼為了復用,對於String變數的賦值語句在編譯時便進行了特殊處理,在常量池中找是否已經存在該字元串,如果有,返回引用,達到復用的目的,如果沒有,將字元串放入常量池返回該引用為了下次復用。而對於其他引用變數傳參,當棧幀B要對傳入參數進行改變的時候,都會進行aload操作,由於jvm是基於棧的位元組碼執行,aload的參數只能是棧幀A中引用的複製,這點區別於C,由於C是基於寄存器的操作,其指針傳遞,操作是的是指針變數本身,可以用一個經典的引用交換實例區分,網上有舉例,不在累述,以上。