1. 程式人生 > >Java引數是傳值還是傳引用

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變數)並沒有影響,說明在傳遞基本型別變數時,實際上是將引數值作為一個副本傳進方法函式的,在呼叫的方法函式中,不管怎麼改變這個值,其結果都只是改變了副本的值,而不會影響到源值。

流程:

  1. 主函式進棧,a初始化。
  2. 呼叫test(int)方法,test(int)方法進棧,將main(String[])中a的
    ,複製一份給test(int a)中的a。
  3. test(int)方法中對a的值進行a++自增運算。
  4. test(int)方法執行完畢,a的值加1。
  5. test(int)方法彈棧。
  6. 主函式彈棧。

示例程式碼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實際上將它的副本傳進方法函式的,這個函式裡面的引用副本指向的是物件的地址,通過引用副本找到地址並修改地址中的值,也就改變了物件。

流程:

  1. 主函式進棧,StringBuffer string初始化。
  2. 呼叫test方法,test( )進棧,將string指向的地址值,複製一份給str。
  3. test方法中,根據地址值,找到堆中的StringBuffer物件,並將它的值改為“Hello,world!”。
  4. test方法執行完畢,StringBuffer的值已經改變。
  5. test方法彈棧。
  6. 主函式彈棧。

示例程式碼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所指向的引用並沒有改變。 

流程:

  1. 主函式進棧,string初始化。
  2. 呼叫test方法,test( )進棧,將string指向的地址值,複製一份給str。
  3. test方法中,重現建立了一個String物件”Hello,world!”,並將str指向了新的地址值。
  4. test方法執行完畢,str所指向的地址值已經改變,但string指向的地址值不變。
  5. test方法彈棧。
  6. 主函式彈棧。

 小結

對於Java引數是傳值還是傳引用這個問題,對於基本型別變數,Java是傳值的副本;對於引用型別變數,即所有的物件型變數,Java都是傳引用的副本。

PS:上述如果有任何理解錯誤的地方,歡迎大家批評指出。