1. 程式人生 > >深入分析java傳參

深入分析java傳參

沒有 init 創建 span 既然 ams 字面量 append urn

概述

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是基於寄存器的操作,其指針傳遞,操作是的是指針變量本身,可以用一個經典的引用交換實例區分,網上有舉例,不在累述,以上。

深入分析java傳參