六、Java 到底是值傳遞還是引用傳遞
将參數傳遞給方法有兩種常見的方式,一種是“值傳遞”,一種是“引用傳遞”。C 語言本身隻支援值傳遞,它的衍生品 C++ 既支援值傳遞,也支援引用傳遞,而 Java 隻支援值傳遞。
01、值傳遞 VS 引用傳遞
首先,我們必須要搞清楚,到底什麼是值傳遞,什麼是引用傳遞,否則,讨論 Java 到底是值傳遞還是引用傳遞就顯得毫無意義。
當一個參數按照值的方式在兩個方法之間傳遞時,調用者和被調用者其實是用的兩個不同的變量——被調用者中的變量(原始值)是調用者中變量的一份拷貝,對它們當中的任何一個變量修改都不會影響到另外一個變量。
而當一個參數按照引用傳遞的方式在兩個方法之間傳遞時,調用者和被調用者其實用的是同一個變量,當該變量被修改時,雙方都是可見的。
Java 程式員之是以容易搞混值傳遞和引用傳遞,主要是因為 Java 有兩種資料類型,一種是基本類型,比如說 int,另外一種是引用類型,比如說 String。
基本類型的變量存儲的都是實際的值,而引用類型的變量存儲的是對象的引用——指向了對象在記憶體中的位址。值和引用存儲在 stack(棧)中,而對象存儲在 heap(堆)中。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SM1QTN1gDNzkzMxQWYhJGOmZjN0EzNjNDNmNjYzIDOl9CX5d2bs92Yl1iclB3bsVmdlR2LcNWaw9CXt92Yu4GZjlGbh5yYjV3Lc9CX6MHc0RHaiojIsJye.png)
之是以有這個差別,是因為:
棧的優勢是,存取速度比堆要快,僅次于直接位于 CPU 中的寄存器。但缺點是,棧中的資料大小與生存周期必須是确定的。
堆的優勢是可以動态地配置設定記憶體大小,生存周期也不必事先告訴編譯器,Java 的垃圾回收器會自動收走那些不再使用的資料。但由于要在運作時動态配置設定記憶體,存取速度較慢。
02、基本類型的參數傳遞
衆所周知,Java 有 8 種基本資料類型,分别是 int、long、byte、short、float、double 、char 和 boolean。它們的值直接存儲在棧中,每當作為參數傳遞時,都會将原始值(實參)複制一份新的出來,給形參用。形參将會在被調用方法結束時從棧中清除。
來看下面這段代碼:
public 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 直接存儲在棧中。
2)調用 modify() 方法的時候,将為實參 age 建立一個副本(形參 age1),它的值也為 18,不過是在棧中的其他位置。
3)對形參 age 的任何修改都隻會影響它自身而不會影響實參。
03、引用類型的參數傳遞
來看一段建立引用類型變量的代碼:
Writer writer = new Writer(18, "沉默王二");
1
writer 是對象嗎?還是對象的引用?為了搞清楚這個問題,我們可以把上面的代碼拆分為兩行代碼:
Writer writer;
writer = new Writer(18, "沉默王二");
2
假如 writer 是對象的話,就不需要通過 new 關鍵字建立對象了,對吧?那也就是說,writer 并不是對象,在“=”操作符執行之前,它僅僅是一個變量。那誰是對象呢?new Writer(18, "沉默王二"),它是對象,存儲于堆中;然後,“=”操作符将對象的引用指派給了 writer 變量,于是 writer 此時應該叫對象引用,它存儲在棧中,儲存了對象在堆中的位址。
每當引用類型作為參數傳遞時,都會建立一個對象引用(實參)的副本(形參),該形參儲存的位址和實參一樣。
public class ReferenceTypeDemo {
public static void main(String[] args) {
Writer a = new Writer(18);
Writer b = new Writer(18);
modify(a, b);
System.out.println(a.getAge());
System.out.println(b.getAge());
}
private static void modify(Writer a1, Writer b1) {
a1.setAge(30);
b1 = new Writer(18);
b1.setAge(30);
}
}
1)在調用 modify() 方法之前,實參 a 和 b 指向的對象是不一樣的,盡管 age 都為 18。
2)在調用 modify() 方法時,實參 a 和 b 都在棧中建立了一個新的副本,分别是 a1 和 b1,但指向的對象是一緻的(a 和 a1 指向對象 a,b 和 b1 指向對象 b)。
3)在 modify() 方法中,修改了形參 a1 的 age 為 30,意味着對象 a 的 age 從 18 變成了 30,而實參 a 指向的也是對象 a,是以 a 的 age 也變成了 30;形參 b1 指向了一個新的對象,随後 b1 的 age 被修改為 30。
修改 a1 的 age,意味着同時修改了 a 的 age,因為它們指向的對象是一個;修改 b1 的 age,對 b 卻沒有影響,因為它們指向的對象是兩個。
程式輸出的結果如下所示:
30
18
果然和我們的分析是吻合的。