Java 值傳遞 or 引用傳遞?

Java 方法傳參 值傳遞 or 引用傳遞?

結論:Java採用的是值傳遞

先建立一些基礎的概念

什麼是值傳遞和引用傳遞?

  • 值傳遞(pass by value):是指在調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中如果對參數進行修改,將不會影響到實際參數
  • 引用傳遞(pass by reference):是指在調用函數時將實際參數的地址直接傳遞到函數中,那麼在函數中對參數所進行的修改,將影響到實際參數

Java的數據類型分為兩類

  • 基本類型(int float等)
  • 引用類型(string, 數組等, 以及一切類對象)

如下圖,展示了兩者在記憶體中存儲形式,基本類型存儲的是值,而引用類型存儲的是地址,該地址指向值所在的記憶體空間。引用類型有點類似於C語言中的指針。

image-20220402224542013

實踐出真知,本文做了三個實驗來論證為什麼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值。

image-20220402231633717

實驗二:下圖表示的是方法內部還未賦值的時候,兩個變數的記憶體情況。兩個變數雖然有著不同的記憶體空間,但是存儲都是Nick的地址,實際指向的是同一個地址空間。有了Nick的存儲地址,當然可以方法內部去改變Nick的值。

image-20220402232001807

賦值後

image-20220402232500029

實驗三:與實驗二相同,在還未創建新的實例時,兩者指向的都是Nick。但是在給方法內部的std賦值之後,實際上改變了其存放的地址,將其指向了一個新的對象Paul。

image-20220402232738402

根據三個實驗的結果,我們可以論證出Java採用的是值傳遞,只不過對於基本類型而言,傳遞的是一個具體的值,而對於引用類型而言傳遞的也是一個具體的值,只不過這個值是一個地址。而有這個地址,我們可以對地址指向的地址空間進行操作,所以會出現實驗二的情況,但是如果我們對值本身進行改變賦值,兩者是互不影響的。

看到一個例子說的很形象:

image-20220402233429207