Java的參數傳遞是「按值傳遞」還是「按引用傳遞」?

Java 編程語言中最大的困惑之一就是: java 是按值傳遞還是按引用傳遞。我在面試中經常會問面試者這個問題,但還是有很多面試者對這個問題的理解不是很正確。
在這裏插入圖片描述
有很多面試者是這樣理解的:

  • 如果傳遞類型爲基礎數據類型,則按值傳遞,
  • 如果傳遞類型爲類,則按引用傳遞。

這樣的理解正確嗎?他們甚至還可以寫出示例代碼來驗證他們的想法,讓我們來一起看一看大多數人是如何驗證“基礎類型按值傳遞,非基礎類型按引用傳遞”這個想法的:

基礎類型數據作爲參數傳遞

/**
 * 基礎類型數據作爲參數傳遞
 * @Author: danding
 * @Date: 2019/11/5
 */
public class TestParams {

    public static void main(String[] args){
        int x = 6;
        System.out.println("x的初始值爲:" + x);
        add(x);
        System.out.println("x的最終值爲:" + x);
    }

    public static void add(int x){
        x = x + 1;
        System.out.println("add 方法中的x值爲:" + x);
    }

}

運行結果:

x的初始值爲:6
add 方法中的x值爲:7
x的最終值爲:6

非基礎類型作爲參數傳遞
首先我們定義一個類

/**
 * 定義一個女朋友的類
 * (簡陋了點,只有年齡,但不影響我們使用呀)
 * @Author: danding
 * @Date: 2019/11/5
 */
public class GrilFriend {
    private int age;

    public GrilFriend(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

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

然後我們將創建實例並作爲參數傳遞

/**
 * 基礎類型數據作爲參數傳遞
 * @Author: danding
 * @Date: 2019/11/5
 */
public class TestParams {

    public static void main(String[] args){
        GrilFriend gril = new GrilFriend(18);
        System.out.println("女朋友的初始年齡爲:" + gril.getAge());
        add(gril);
        System.out.println("女朋友的最終年齡爲:" + gril.getAge());
    }

    private static void add(GrilFriend friend){
        friend.setAge(friend.getAge()+1);
        System.out.println("女朋友在方法中的年齡爲:" + friend.getAge());
    }

}

運行結果:

女朋友的初始年齡爲:18
女朋友在方法中的年齡爲:19
女朋友的最終年齡爲:19

非基礎類型作爲參數傳遞時,值的確被修改了。

這個時候很多同學通過以上兩個示例驗證,自己就得出了自己的結論:

如果傳遞類型爲基礎數據類型,則按值傳遞,否則爲按引用傳遞。

在此說明,這個理解是錯誤的,錯誤的,錯誤的。下面我們就來說說 Java中的參數傳遞到底是按值傳遞還是按引用傳遞?

首先說下正確的答案:Java 的參數傳遞,不管是基本數據類型還是引用類型的參數,都是按值傳遞,沒有按引用傳遞!

首先,我們應該瞭解按值傳遞或按引用傳遞的含義。

  • 按值傳遞:將方法參數值複製到另一個變量,然後傳遞複製的對象,將其稱爲按值傳遞。
  • 按引用傳遞:將對實際參數的別名或引用傳遞給方法,將其稱爲按引用傳遞的原因。

你個糟老頭子壞得狠,我信你個鬼,你這個解釋給我要給差評…

且聽老夫(哦,不,是小編)慢慢道來…

當一個對象被當作參數傳遞到一個方法後,在此方法內可以改變這個對象的屬性,那麼這裏到底是「按值傳遞」還是「按引用傳遞」?

答:是按值傳遞。Java 語言的參數傳遞只有「按值傳遞」。當一個實例對象作爲參數被傳遞到方法中時,參數的值就是該對象的引用的一個副本。指向同一個對象,對象的內容可以在被調用的方法內改變,但對象的引用(不是引用的副本) 是永遠不會改變的。

基礎類型參數傳遞

這個上面的示例已經驗證了,爲按值傳遞,這個大家應該不會有什麼異議。

非基礎類型參數傳遞

我們重點來說下對象類型作爲參數傳遞

先來看一下傳遞的例子:

public class TestParams {

    public static void main(String[] args){
        Person p1 = new Person();
        System.out.println(p1);
        change(p1);
        System.out.println(p1);
    }

    private static void change(Person p2){
        p2 = new Person();
    }

}

class Person{

}

運行結果

Person@677327b6
Person@677327b6

可以看出兩次打印person的地址值是一樣的,即調用完change() 方法之後,person變量並沒有發生改變。

這個傳遞過程的示意圖如下:

當執行到Person p1 = new Person();代碼時,程序在堆內存中開闢了一塊內存空間用來存儲Person類的實例對象,同時在棧內存中開闢了一個存儲單元用來存儲該實例對象的引用,即上圖中person指向的存儲單元。

當執行到change(p1);代碼時,person作爲參數傳遞給change()方法,需要注意的是:person將自己存儲單元的內容傳遞給了change()方法的p2變量!此後,在change()方法中對p2的一切操作都是針對p2所指向的存儲單元,與person所指向的那個存儲單元沒有關係了!

這個時候該有同學說了,那上面那個女朋友示例中,女朋友的年齡不是被在方法中修改了嗎?如果傳遞的是副本那不應該修改不了女朋友的年齡嗎?

如果我們將女朋友中的代碼放到內存示例圖中走一遍,你應該就明白其中的道理了。
所謂引用副本,但其所指向的還是真實的對象,所以修改的還是真實對象上的屬性。

我希望上面的解釋能消除所有疑問,只需要記住Java 的參數傳遞,不管是基本數據類型還是引用類型的參數,都是按值傳遞,沒有按引用傳遞!。當您將瞭解堆空間和棧內存以及存儲不同對象和引用的位置時,將會更加清楚,有關程序的詳細說明,請閱讀 Java Heap vs Stack


“不積跬步,無以至千里”,希望未來的你能:有夢爲馬 隨處可棲!加油,少年!

關注公衆號:「Java 知己」,每天更新Java知識哦,期待你的到來!

  • 發送「Group」,與 10 萬程序員一起進步。
  • 發送「面試」,領取BATJ面試資料、面試視頻攻略。
  • 發送「玩轉算法」,領取《玩轉算法》系列視頻教程。
  • 千萬不要發送「1024」…
    每日福利
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章