JAVA 到底是值傳遞還是引用傳遞?

Java 到底是值傳遞還是引用傳遞呢?可能我們背過很多次,說 java 是值傳遞(當然,網上也有一些同學信誓旦旦的說 java 是引用傳遞,錯誤),但是當我突然再問你 java 爲什麼是值傳遞的?那可能就有點懵了。

1. 形參和實參

JAVA 是一種面向對象的編程語言,一個類中有屬性和方法,我們這裏重點說下方法的定義。

package com.donkey;

public class Person {

    private String name;

    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int changeAge(int age) {
        age = age+1;
        System.out.println("changeAge方法執行後的年齡:" + age);
        return age;
    }

    public static void main(String[] args) {
        Person person = new Person();
        person.changeAge(10);
    }
}

我們重點看一下 changeAge(int age) 這個方法,這是一個有參的方法,在 main 方法中調用 changeAge 方法,並且傳遞參數 age =1;

說到這裏,我要引出兩個概念:形式參數(形參)和實際參數(實參);

形參:是在定義函數名和函數體的時候使用的參數, 目的是用來接收調用該函數時傳入的參數;

實參:在調用有參函數時,主調函數和被調函數之間有數據傳遞關係。在主調函數中調用一個函數時,函數名後面括號中的參數稱爲 “實際參數”。

如上面的例子中,changeAge(int age) 這個方法中的 age 就是形參,而調用這個方法時傳遞的 10 就是實參。

即:實參是調用有參方法的時候真正傳遞的內容,而形參是用於接收實參內容的參數。

2. 值傳遞與引用傳遞

通過上面,我們知道了形參和實參,那麼在調用方法的時候,我們怎麼傳遞的呢?或者說傳遞的是什麼呢?這就引出了下面的概念:值傳遞和引用傳遞。

值傳遞:調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中如果對參數進行修改,將不會影響到實際參數。

引用傳遞:指在調用函數時將實際參數的地址直接傳遞到函數中,那麼在函數中對參數所進行的修改,將影響到實際參數。

基於上面的理論知識,我們看下面這段程序:

public static void main(String[] args) {
    Person person = new Person();
    int age = 20;
    person.changeAge(age);
    System.out.println("main方法中的年齡:" + age);
}

輸出的結果爲:

changeAge方法執行後的年齡:21
main方法中的年齡:20

從結果可知,changeAge 方法並沒有改變實際參數 age 的值,實參 age 還是等於 20。所以我們得出結論:java 是值傳遞。

但真實情況可能並不是這麼簡單,因爲我們傳遞是一個基本數據類型,那如果是傳一個引用類型的實參呢?那再看下面的小程序:

public String changeName(String name) {
    name = "zhangsan";
    System.out.println("changeName方法執行後的名字:" + name);
    return name;
}

public static void main(String[] args) {
    Person person = new Person();
    String name ="lisi";
    person.changeName(name);
    System.out.println("main方法中的name:" + name);
}

輸出結果如下:

changeName方法執行後的名字:zhangsan
main方法中的name:lisi

顯然,changeName 方法並沒有改變實參 name 的值,main 方法中的 name 還是 lisi。所以:java 是值傳遞。

可能你說,你這個實驗的不對,要是傳遞一個對象,就不是這樣的了,那麼我們以 user 爲例,再看下面的小程序:

public void changPerson(Person person) {
    person.setAge(30);
    person.setName("zhangfei");
    System.out.println("changPerson方法執行後的person::" + person);
}

@Override
public String toString() {
    return "Person{" +
            " + name + '\'' +
            "age='" + age + '\'' +
            '}';
}

public static void main(String[] args) {
    Person person = new Person();
    person.setAge(45);
    person.setName("gunayu");
    person.changPerson(person);
    System.out.println("main方法中的person:" + person);

}

執行結果如下:

changPerson方法執行後的person::Person{name=‘zhangfei’, age=‘30’}
main方法中的person:Person{name=‘zhangfei’, age=‘30’}

哦哦哦,這時候你可能會說,看看吧,person 的內容改變了,這說明 java 在傳遞對象的時候是引用傳遞。那麼這個結論正確麼?如果這個結論不正確,那它錯在哪裏呢?

3.JAVA 中的值傳遞

通過上面的三個小程序,我們得出來了不同的結論,這也是我當初怎麼也無法理解這個概念的原因。其實上面的理論沒有問題,只是我們實驗的方法由問題。通過上面值傳遞和引用傳遞的概念,我們知道,這兩個的概念的最核心的區別就是值傳遞過程中或不會重新複製一份副本出來。

我們再來舉個比較形象的例子。

當你的同事想開你的車出差,然後你有一把車鑰匙,你直接把這把車鑰匙給了同事。假如這個同時給車鑰匙加了一個鑰匙套,那麼當同事把車鑰匙還給你的時候,這把鑰匙就會包裹了一個鑰匙套,這就是引用傳遞。

當你的同事想開你的車出差,你有一把車鑰匙,然後你找 4S 店複製了一把車鑰匙,你把新複製的這把車鑰匙給了同事。假如這個同時給車鑰匙加了一個鑰匙套,那麼當同事把車鑰匙還給你的時候,你手裏的這把原始鑰匙依然沒有鑰匙套,這就是值傳遞。

下面我們再看看一個小程序:

    public void changPerson1(Person person) {
        person = new Person();
        person.setAge(30);
        person.setName("zhangfei");
        System.out.println("changPerson1::" + person);
    }
    public static void main(String[] args) {
        Person guanyu = new Person();
        guanyu.setAge(45);
        guanyu.setName("gunayu");
        guanyu.changPerson1(guanyu);
        System.out.println("main方法中的person:" + guanyu);

    }

執行結果如下:

changPerson1::Person{name=‘zhangfei’, age=‘30’}
main方法中的person:Person{name=‘gunayu’, age=‘45’}

當我們在 main 中創建一個 Person 對象的時候,在堆中開闢一塊內存,其中保存了 name 和 age 數據。然後 guanyu 持有該內存的地址 0x00000001

當嘗試調用 changPerson1 方法,並且 guanyu 作爲實際參數傳遞給形式參數 person 的時候,會把這個地址 0x00000001 交給 person,這時,person 也指向了這個地址

然後在 changPerson1 方法內對參數進行修改的時候,即 person= new Person();,會重新開闢一塊 0x00000002 的內存,賦值給 person。後面對 user 的任何修改都不會改變內存 0x00000001` 的內容,

上面這種傳遞是什麼傳遞?肯定不是引用傳遞,如果是引用傳遞的話,在執行 person= new Person(); 的時候,實際參數的引用也應該改爲指向 0x00000001,但是實際上並沒有。

所以,java 在傳遞對象時,傳遞是堆中的那一小塊內存區域,而並不是 person 這個引用的本身。

** 所以,值傳遞和引用傳遞的區別並不是傳遞的內容。而是實參到底有沒有被複制一份給形參。** 在判斷實參內容有沒有受影響的時候,要看傳的的是什麼,如果你傳遞的是個地址,那麼就看這個地址的變化會不會有影響,而不是看地址指向的對象的變化。就像車鑰匙和車的關係,車鑰匙通過引用執行車,所以如果是引用傳遞的話,變化的應該是車鑰匙而不是引用的那輛車。

其實也好理解,guanyu = new Person(), 其實完全就可以把那小塊對空間理解成 guanyu 所對應的值。

4. 小結

無論是值傳遞還是引用傳遞,其實都是一種求值策略 (Evaluation strategy)。在求值策略中,還有一種叫做按共享傳遞 (call by sharing)。其實 Java 中的參數傳遞嚴格意義上說應該是按共享傳遞。

按共享傳遞,是指在調用函數時,傳遞給函數的是實參的地址的拷貝(如果實參在棧中,則直接拷貝該值)。在函數內部對參數進行操作時,需要先拷貝的地址尋找到具體的值,再進行操作。如果該值在棧中,那麼因爲是直接拷貝的值,所以函數內部對參數進行操作不會對外部變量產生影響。如果原來拷貝的是原值在堆中的地址,那麼需要先根據該地址找到堆中對應的位置,再進行操作。因爲傳遞的是地址的拷貝所以函數內對值的操作對外部變量是可見的。

即:Java 中的傳遞,是值傳遞,如果是引用類型這個值,實際上傳遞的是對象的引用(即棧與對之間的那條指向的線,每次傳遞時,其實是複製出了這樣一條線,只不過線的起點換成了形參,終點沒有變)。

————————————————

版權聲明:本文爲 CSDN 博主「泗水長流」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。

原文鏈接:

https://blog.csdn.net/lvxinchun/article/details/116140524

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/rTfkKemAQZlqV5q9bNMTyw