值傳遞與引用傳遞
“哥,說(shuō)說(shuō) Java 到底是值傳遞還是引用傳遞吧?”三妹一臉的困惑,看得出來(lái)她被這個(gè)問(wèn)題折磨得不輕。
“說(shuō)實(shí)在的,我在一開(kāi)始學(xué) Java 的時(shí)候也被這個(gè)問(wèn)題折磨得夠嗆,總以為基本數(shù)據(jù)類型在傳參的時(shí)候是值傳遞,而引用類型是引用傳遞。”我對(duì)三妹袒露了心聲,為的就是讓她不再那么焦慮,她哥當(dāng)年也是這么過(guò)來(lái)的。
C 語(yǔ)言是很多編程語(yǔ)言的母胎,包括 Java,那么對(duì)于 C 語(yǔ)言來(lái)說(shuō),所有的方法參數(shù)都是“通過(guò)值”傳遞的,也就是說(shuō),傳遞給被調(diào)用方法的參數(shù)值存放在臨時(shí)變量中,而不是存放在原來(lái)的變量中。這就意味著,被調(diào)用的方法不能修改調(diào)用方法中變量的值,而只能修改其私有變量的臨時(shí)副本的值。
Java 繼承了 C 語(yǔ)言這一特性,因此 Java 是按照值來(lái)傳遞的。
接下來(lái),我們必須得搞清楚,到底什么是值傳遞(pass by value),什么是引用傳遞(pass by reference),否則,討論 Java 到底是值傳遞還是引用傳遞就顯得毫無(wú)意義。
當(dāng)一個(gè)參數(shù)按照值的方式在兩個(gè)方法之間傳遞時(shí),調(diào)用者和被調(diào)用者其實(shí)是用的兩個(gè)不同的變量——被調(diào)用者中的變量(原始值)是調(diào)用者中變量的一份拷貝,對(duì)它們當(dāng)中的任何一個(gè)變量修改都不會(huì)影響到另外一個(gè)變量,據(jù)說(shuō) Fortran 語(yǔ)言是通過(guò)引用傳遞的。
“Fortran 語(yǔ)言?”三妹睜大了雙眼,似乎聽(tīng)見(jiàn)了什么新的名詞。
“是的,F(xiàn)ortran 語(yǔ)言,1957 年由 IBM 公司開(kāi)發(fā),是世界上第一個(gè)被正式采用并流傳至今的高級(jí)編程語(yǔ)言。”
當(dāng)一個(gè)參數(shù)按照引用傳遞的方式在兩個(gè)方法之間傳遞時(shí),調(diào)用者和被調(diào)用者其實(shí)用的是同一個(gè)變量,當(dāng)該變量被修改時(shí),雙方都是可見(jiàn)的。
“我們之所以容易搞不清楚 Java 到底是值傳遞還是引用傳遞,主要是因?yàn)?Java 中的兩類數(shù)據(jù)類型的叫法容易引發(fā)誤會(huì),比如說(shuō) int 是基本類型,說(shuō)它是值傳遞的,我們就很容易理解;但對(duì)于引用類型,比如說(shuō) String,說(shuō)它也是值傳遞的時(shí)候,我們就容易弄不明白。”
我們來(lái)看看基本數(shù)據(jù)類型和引用數(shù)據(jù)類型之間的差別。
int age = 18;
String name = "二哥";
age 是基本類型,值就保存在變量中,而 name 是引用類型,變量中保存的是對(duì)象的地址。一般稱這種變量為對(duì)象的引用,引用存放在棧中,而對(duì)象存放在堆中。
這里說(shuō)的棧和堆,是指內(nèi)存中的一塊區(qū)域,和數(shù)據(jù)結(jié)構(gòu)中的棧和堆不一樣。棧是由編譯器自動(dòng)分配釋放的,所以適合存放編譯期就確定生命周期的數(shù)據(jù);而堆中存放的數(shù)據(jù),編譯器是不需要知道生命周期的,創(chuàng)建后的回收工作由垃圾收集器來(lái)完成。
“畫(huà)幅圖。”

當(dāng)用 = 賦值運(yùn)算符改變 age 和 name 的值時(shí)。
age = 16;
name = "三妹";
對(duì)于基本類型 age,賦值運(yùn)算符會(huì)直接改變變量的值,原來(lái)的值被覆蓋。
對(duì)于引用類型 name,賦值運(yùn)算符會(huì)改變對(duì)象引用中保存的地址,原來(lái)的地址被覆蓋,但原來(lái)的對(duì)象不會(huì)被覆蓋。

“三妹,注意聽(tīng),接下來(lái),我們來(lái)說(shuō)說(shuō)基本數(shù)據(jù)類型的參數(shù)傳遞。”
Java 有 8 種基本數(shù)據(jù)類型,分別是 int、long、byte、short、float、double 、char 和 boolean,就拿 int 類型來(lái)舉例吧。
class PrimitiveTypeDemo {
public static void main(String[] args) {
int age = 18;
modify(age);
System.out.println(age);
}
private static void modify(int age1) {
age1 = 30;
}
}
1)main() 方法中的 age 為基本類型,所以它的值 18 直接存儲(chǔ)在變量中。
2)調(diào)用 modify() 方法的時(shí)候,將會(huì)把 age 的值 18 復(fù)制給形參 age1。
3)modify() 方法中,對(duì) age1 做出了修改。
4)回到 main() 方法中,age 的值仍然為 18,并沒(méi)有發(fā)生改變。
如果我們想讓 age 的值發(fā)生改變,就需要這樣做。
class PrimitiveTypeDemo1 {
public static void main(String[] args) {
int age = 18;
age = modify(age);
System.out.println(age);
}
private static int modify(int age1) {
age1 = 30;
return age1;
}
}
第一,讓 modify() 方法有返回值;
第二,使用賦值運(yùn)算符重新對(duì) age 進(jìn)行賦值。
“好了,再來(lái)說(shuō)說(shuō)引用類型的參數(shù)傳遞。”
就以 String 為例吧。
class ReferenceTypeDemo {
public static void main(String[] args) {
String name = "二哥";
modify(name);
System.out.println(name);
}
private static void modify(String name1) {
name1 = "三妹";
}
}
在調(diào)用 modify() 方法的時(shí)候,形參 name1 復(fù)制了 name 的地址,指向的是堆中“二哥”的位置。

當(dāng) modify() 方法調(diào)用結(jié)束后,改變了形參 name1 的地址,但 main() 方法中 name 并沒(méi)有發(fā)生改變。

總結(jié):
Java 中的參數(shù)傳遞是按值傳遞的。 如果參數(shù)是基本類型,傳遞的是基本類型的字面量值的拷貝。 如果參數(shù)是引用類型,傳遞的是引用的對(duì)象在堆中地址的拷貝。
“好了,三妹,今天的學(xué)習(xí)就到這吧。”
PS:點(diǎn)擊「閱讀原文」可直達(dá)《教妹學(xué)Java》專欄的 GitHub 開(kāi)源地址,記得 star 哦!
