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」…