死磕面試系列,Java到底是值傳遞還是參照傳遞?

2022-11-04 12:02:17

Java到底是值傳遞還是參照傳遞?

這雖然是一個老生常談的問題,但是對於沒有深入研究過這塊,或者Java基礎不牢的同學,還是很難回答得讓人滿意。

可能很多同學能夠很輕鬆的背出JVM、分散式事務、高並行、秒殺系統、領域模型等高難度問題,但是對於Java基礎問題不屑一顧。這種抓大放小的初衷是對的,要是碰到深究基礎細節的面試官,就抓瞎了。

今天一燈帶你一塊深入剖析Java傳遞的底層原理,看完這篇文章再去面試,面試官肯定要豎起大拇哥誇你:

「小夥子,你是懂Java傳遞的!」

1. 什麼是形參和實參

形參: 就是形式引數,用於定義方法的時候使用的引數,是用來接收呼叫者傳遞的引數的。

實參: 就是實際引數,用於呼叫時傳遞給方法的引數。實參在傳遞給別的方法之前是要被預先賦值的。

/**
 * @author 一燈架構
 * @apiNote Java傳遞範例
 **/
public class Demo {

    public static void main(String[] args) {
        String name = "一燈架構"; // 這裡的name就是實際引數
        update(name);
        System.out.println(name);
    }

    // 這裡方法參數列中name就是形式引數
    private static void update(String name) {
        // doSomething
    }

}

在Java方法呼叫的過程中,就是把實參傳遞給形參,形參的作用域在方法內部。

2. 什麼是值傳遞和參照傳遞

值傳遞: 是指在呼叫方法時,將實際引數拷貝一份傳遞給方法,這樣在方法中修改形式引數時,不會影響到實際引數。

參照傳遞: 也叫地址傳遞,是指在呼叫方法時,將實際引數的地址傳遞給方法,這樣在方法中對形式引數的修改,將影響到實際引數。

也就是說值傳遞,傳遞的是副本。參照傳遞,傳遞的是實際記憶體地址。這是兩者的本質區別,下面會用到。

3. 測試驗證

3.1 基本資料型別驗證

先用基本資料型別驗證一下:

/**
 * @author 一燈架構
 * @apiNote Java傳遞範例
 **/
public class Demo {

    public static void main(String[] args) {
        int count = 0;
        update(count);
        System.out.println("main方法中count:" + count);
    }

    private static void update(int count) {
        count++;
        System.out.println("update方法中count:" + count);
    }

}

輸出結果:

update方法中count:1
main方法中count:0

可以看到雖然update方法修改了形參count的值,但是main方法中實參count的值並沒有變,但是為什麼沒有變?我們深究一下底層原理。

我們都知道Java基本資料型別是儲存在虛擬機器器棧記憶體中,棧中存放著棧幀,方法呼叫的過程,就是棧幀在棧中入棧、出棧的過程。

當執行main方法的時候,就往虛擬機器器棧中壓入一個棧幀,棧幀中儲存的區域性變數資訊是count=0。

當執行update方法的時候,再往虛擬機器器棧中壓入一個棧幀,棧幀中儲存的區域性變數資訊是count=0。

修改update棧幀中資料,顯然不會影響到main方法棧幀的資料。

3.2 參照型別驗證

再用參照型別資料驗證一下:

/**
 * @author 一燈架構
 * @apiNote Java傳遞範例
 **/
public class Demo {

    public static void main(String[] args) {
        User user = new User();
        user.setId(0);
        update(user);
        System.out.println("main方法中user:" + user);
    }

    private static void update(User user) {
        user = new User();
        user.setId(1);
        System.out.println("update方法中user:" + user);
    }

}

輸出結果:

update方法中user:User(id=1)
main方法中user:User(id=0)

由程式碼得知,update方法中重新初始化了user物件,並重新賦值,並不影響main方法中實引資料。

當執行main方法時,會在堆記憶體中開闢一塊記憶體,在棧記憶體中壓入一個棧幀,棧幀中儲存一個參照,指向堆記憶體中的地址。

當呼叫update方法時,會把main方法的棧幀拷貝一份,再壓入棧記憶體中,指向同一個堆記憶體地址。

當執行update方法,重新初始化user物件,並重新賦值的時候。會在堆記憶體中再開闢一塊記憶體,再把棧記憶體中update棧幀指向新的堆記憶體地址,並修改新的堆記憶體中的資料。

從這裡可以看出是值傳遞,修改了形參裡面資料,實參並沒有跟著變化。

3.3 同一地址的參照型別驗證

/**
 * @author 一燈架構
 * @apiNote Java傳遞範例
 **/
public class Demo {

    public static void main(String[] args) {
        User user = new User();
        user.setId(0);
        update(user);
        System.out.println("main方法中user:" + user);
    }

    private static void update(User user) {
        user.setId(1);
        System.out.println("update方法中user:" + user);
    }

}

輸出結果:

update方法中user:User(id=1)
main方法中user:User(id=1)

可以看出update方法修改user物件的屬性,main方法中user物件也跟著變了。

這是不是說明Java支援參照傳遞呢?

並不是。這裡在引數傳遞的過程中,只是把實參的地址拷貝了一份傳遞給形參,update方法中只修改了引數地址裡面的內容,並沒有對形參本身進行修改。

4. 總結

經過上述分析,Java引數傳遞中,不管傳遞的是基本資料型別還是參照型別,都是值傳遞

當傳遞基本資料型別,比如原始型別(int、long、char等)、包裝型別(Integer、Long、String等),實參和形參都是儲存在不同的棧幀內,修改形參的棧幀資料,不會影響實參的資料。

當傳參的參照型別,形參和實參指向同一個地址的時候,修改形參地址的內容,會影響到實參。當形參和實參指向不同的地址的時候,修改形參地址的內容,並不會影響到實參。

我是「一燈架構」,如果本文對你有幫助,歡迎各位小夥伴點贊、評論和關注,感謝各位老鐵,我們下期見