死磕面試系列,Java到底是值傳遞還是引用傳遞?
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等),實參和形參都是存儲在不同的棧幀內,修改形參的棧幀數據,不會影響實參的數據。
當傳參的引用類型,形參和實參指向同一個地址的時候,修改形參地址的內容,會影響到實參。當形參和實參指向不同的地址的時候,修改形參地址的內容,並不會影響到實參。
我是「一燈架構」,如果本文對你有幫助,歡迎各位小夥伴點贊、評論和關注,感謝各位老鐵,我們下期見