在逛 Stack Overflow 的時候,發現了一些訪問量像阿爾卑斯山一樣高的問題,比如說這個:Java 到底是值傳遞還是引用傳遞?訪問量足足有 188萬+,這不得了啊!說明有很多很多的程式設計師被這個問題困擾過。實話實說吧,我就是其中之一。
來回顧一下提問者的問題:
我一直認為 Java 是按引用傳遞的,但是我看一些部落格上說不是的。我就納悶了,Java 到底是值傳遞還是引用傳遞?值傳遞和引用傳遞有什麼區別呢?
如果你也曾被這個問題困擾過,或者正在被困擾,就請隨我一起來梳理一下問題的答案。打怪進階嘍!
01、值傳遞和引用傳遞
什麼是值傳遞,什麼是引用傳遞?我們需要先把這兩個定義搞清楚,才能搞清楚 Java 是按值傳遞還是按引用傳遞。
值傳遞(pass by value)是指在呼叫方法時將實參複製一份傳遞到方法中,這樣當方法對形參進行修改時不會影響到實參。
引用傳遞(pass by reference)是指在呼叫方法時將實參的地址直接傳遞到方法中,那麼在方法中對形參所進行的修改,將影響到實參。
上面是比較官方的定義,讀起來不免生硬。在我看來,值傳遞和引用傳遞的關鍵區別有兩點:
1)呼叫方法時有沒有對實參進行復制。
2)方法內對形參的修改會不會影響到實參。
what?值傳遞和引用傳遞還沒有搞清楚,又來兩個新名詞:實參和形參。別急,別急。
02、實參和形參
實參和形參理解起來比值傳遞和引用傳遞容易的多,前者就好像是一元一次方程,後者就像是一元二次方程。
形參:定義方法名和方法體的時候使用的引數,目的是用來接收呼叫該方法時傳入的引數。
實參:在呼叫有參方法時傳入的引數,方法名後面的括號中的引數通常被稱為“實參”。
大家應該都寫過“hello world”程式了,就像下面這樣。
public class Cmower {
public static void main(String[] args) {
System.out.println("hello world");
}
}
複製程式碼
其中 args
就相當於是形參,而字串“hello world”就相當於是實參。如果覺得這個例子不容易理解,那再來看一個。
public class Cmower {
public static void main(String[] args) {
Cmower cmower = new Cmower();
cmower.sop("沉默王二");
}
public void sop(String name) {
System.out.println("hello " + name);
}
}
複製程式碼
其中“沉默王二”為實參;有參方法 sop(String name)
中的 name 為形參。形參就好像實參與被呼叫方法之間的一個橋樑,否則呼叫者沒法傳遞引數,被呼叫的方法無法接收引數。
03、基本型別是值傳遞的
Java 中的資料型別可以分為兩種,一種是基本型別,一種是引用型別。我相信大家在看本篇文章之前,就能夠達成這樣一個共識:基本型別是值傳遞的。這一點毫無疑問。
public class Cmower {
public static void main(String[] args) {
Cmower cmower = new Cmower();
int age = 18;
cmower.sop(age);
System.out.println("main 中的 age " + age);
}
public void sop(int age) {
age = 28;
System.out.println("sop 中的 age " + age);
}
}
複製程式碼
上面這段程式碼中,sop()
方法的實參 age 為 18,儘管 sop()
方法的形參被修改為 28,但並不會影響實參的值。這一點可以從輸出結果中加以證明。
sop 中的 age 28
main 中的 age 18
複製程式碼
具體的執行過程如下圖所示。
04、引用型別是值傳遞嗎?
大家之所以不確定 Java 是值傳遞的還是引用傳遞的,原因就出在這個引用型別上面。單從字面的意思上就容易搞混:引用型別不是引用傳遞難道還是值傳遞?
public class Cmower {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) {
Cmower cmower = new Cmower();
cmower.setName("沉默王二");
cmower.sop(cmower);
System.out.println("main 中的 cmower " + cmower.getName());
}
public void sop(Cmower cmower) {
cmower.setName("沉默王三");
System.out.println("sop 中的 cmower " + cmower.getName());
}
}
複製程式碼
在 main()
方法中,我們通過 new 關鍵字建立了一個物件 cmower,並將其 name 屬性設定為“沉默王二”;然後將實參 cmower 傳遞給 sop()
方法,在 sop()
方法中將形參 cmower 的 name 屬性修改為“沉默王三”。輸出結果是什麼樣子呢?
sop 中的 cmower 沉默王三
main 中的 cmower 沉默王三
複製程式碼
呀!實參 cmower 的屬性 name 竟然不是“沉默王二”而是“沉默王三”了!看看,看看,Java 不是值傳遞吧?
別急別急。我們在 main 方法中追加幾行程式碼。
Cmower cmower = new Cmower();
cmower.setName("沉默王二");
Cmower old = cmower;
cmower.sop(cmower);
System.out.println("main 中的 cmower " + cmower.getName());
System.out.println(old == cmower);
複製程式碼
old == cmower
會是 true 還是 false 呢?閉上眼睛想一想。如果實在是想不出,拋一枚硬幣吧,反正不是 true 就是 false。假如引用型別是引用傳遞的,根據引用傳遞的定義(形參的修改將會影響到實參),那麼結果一定就是 false。
我們來看一下輸出結果:
sop 中的 cmower 沉默王三
main 中的 cmower 沉默王三
true
複製程式碼
true?開什麼玩笑?
不好意思,沒有開玩笑。Java 的確是值傳遞的。只不過,引用型別在呼叫有參方法的時候,傳遞的是物件的引用,並不是物件本身。而物件的引用在傳遞的過程中並沒有發生改變,雖然物件本身發生了變化。可以通過下面這幅圖感受一下。
這下理解了吧?
05、總結
來看下面這段程式碼。
int age = 18;
String name = "沉默王二";
複製程式碼
age 是基本型別,所以值就直接儲存在變數中;而 name 是引用型別,變數中儲存的只是物件的記憶體地址,這種變數一般稱之為物件的引用。
基本型別作為引數被傳遞時肯定是值傳遞;引用型別作為引數被傳遞時也是值傳遞,只不過“值”為對應的引用。
好了各位讀者朋友們,以上就是本文的全部內容了。能看到這裡的都是最優秀的程式設計師,我必須要為大家點個贊?。如果覺得不過癮,還想看到更多,我再推薦幾篇給大家。
370W+程式設計師關注過的問題:如何比較 Java 的字串? 250W+程式設計師關注過的問題:什麼是 NullPointerException? 50W+程式設計師關注過的問題:為什麼會發生ArrayIndexOutOfBoundsException?
有收穫?就點贊、留言,讓更多的人看到這篇文章。
如果想要第一時間看到我更新的文章,可以微信搜尋「沉默王二」,關注我的公眾號,回覆「java」再送你一份精選電子書大禮包,包含這十年來我讀過的最優質的 Java 書籍。