Java到底是值傳遞還是參照傳遞?
這雖然是一個老生常談的問題,但是對於沒有深入研究過這塊,或者Java基礎不牢的同學,還是很難回答得讓人滿意。
可能很多同學能夠很輕鬆的背出JVM、分散式事務、高並行、秒殺系統、領域模型等高難度問題,但是對於Java基礎問題不屑一顧。這種抓大放小的初衷是對的,要是碰到深究基礎細節的面試官,就抓瞎了。
今天一燈帶你一塊深入剖析Java傳遞的底層原理,看完這篇文章再去面試,面試官肯定要豎起大拇哥誇你:
「小夥子,你是懂Java傳遞的!」
形參: 就是形式引數,用於定義方法的時候使用的引數,是用來接收呼叫者傳遞的引數的。
實參: 就是實際引數,用於呼叫時傳遞給方法的引數。實參在傳遞給別的方法之前是要被預先賦值的。
/**
* @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方法呼叫的過程中,就是把實參傳遞給形參,形參的作用域在方法內部。
值傳遞: 是指在呼叫方法時,將實際引數拷貝一份傳遞給方法,這樣在方法中修改形式引數時,不會影響到實際引數。
參照傳遞: 也叫地址傳遞,是指在呼叫方法時,將實際引數的地址傳遞給方法,這樣在方法中對形式引數的修改,將影響到實際引數。
也就是說值傳遞,傳遞的是副本。參照傳遞,傳遞的是實際記憶體地址。這是兩者的本質區別,下面會用到。
先用基本資料型別驗證一下:
/**
* @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方法棧幀的資料。
再用參照型別資料驗證一下:
/**
* @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棧幀指向新的堆記憶體地址,並修改新的堆記憶體中的資料。
從這裡可以看出是值傳遞,修改了形參裡面資料,實參並沒有跟著變化。
/**
* @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方法中只修改了引數地址裡面的內容,並沒有對形參本身進行修改。
經過上述分析,Java引數傳遞中,不管傳遞的是基本資料型別還是參照型別,都是值傳遞。
當傳遞基本資料型別,比如原始型別(int、long、char等)、包裝型別(Integer、Long、String等),實參和形參都是儲存在不同的棧幀內,修改形參的棧幀資料,不會影響實參的資料。
當傳參的參照型別,形參和實參指向同一個地址的時候,修改形參地址的內容,會影響到實參。當形參和實參指向不同的地址的時候,修改形參地址的內容,並不會影響到實參。
我是「一燈架構」,如果本文對你有幫助,歡迎各位小夥伴點贊、評論和關注,感謝各位老鐵,我們下期見