Java深入學(xué)習(xí)系列之值傳遞Or引用傳遞?
我們來(lái)看一個(gè)新手甚至寫(xiě)了多年Java的朋友都可能不是十分確定的問(wèn)題:
在Java方法傳參時(shí),究竟是引用傳遞還是值傳遞?
為了說(shuō)明問(wèn)題, 我給出一個(gè)非常簡(jiǎn)單的class定義:
- public class Foo {
- String attribute;
- Foo(String s) {
- this.attribute = s;
- }
- void setAttribute(String s) {
- this.attribute = s;
- }
- String getAttribute() {
- return this.attribute;
- }
- }
下面在闡明觀點(diǎn)時(shí),可能會(huì)多次用到該類。
關(guān)于Java里值傳遞還是引用傳遞,至少?gòu)谋憩F(xiàn)形式上來(lái)看,兩種觀點(diǎn)都有支撐的論據(jù)。下面我來(lái)一一分析:
觀點(diǎn)1:引用傳遞
理由如下:先看一段代碼
- public class Main {
- public static void modifyReference(Foo c){
- c.setAttribute("c"); // line DDD
- }
- public static void main(String[] args) {
- Foo fooRef = new Foo("a"); // line AAA
- modifyReference(fooRef); // line BBB
- System.out.println(fooRef.getAttribute()); // 輸出 c
- }
- }
上述示例,輸出結(jié)果為"c",而不是"c"。
我們?cè)趌ine AAA處新創(chuàng)建了一個(gè)Object Foo并將其引用fooRef在line BBB處傳給了方法modifyReference()的參數(shù)cRef, 該方法內(nèi)部處理后,fooRef指向的Object中的值從"a"變成了"c", 而引用fooRef還是那個(gè)引用, 因此,我們是否可以認(rèn)為,在line BBB處發(fā)生了引用傳遞?
先留著疑問(wèn),我們繼續(xù)往下看。
觀點(diǎn)2:值傳遞
繼續(xù)看一段代碼
- public class Main {
- public static void changeReference(Foo aRef){
- Foo bRef = new Foo("b");
- aRef = bRef; // line EEE
- }
- public static void main(String[] args) {
- Foo fooRef = new Foo("a"); // line AAA
- changeReference(fooRef); // line BBB
- System.out.println(fooRef.getAttribute()); // 輸出 a
- }
- }
上述示例,輸出結(jié)果為"a", 而不是"b"。
我們?cè)趌ine AAA處新創(chuàng)建了一個(gè)Object Foo并將其引用fooRef在line EEE處傳給了方法changeReference()的參數(shù)aRef, 該方法內(nèi)部引用aRef在line DDD處被重新賦值。如果是引用傳遞,那么引用aRef在line EEE處已經(jīng)被指向了新的Object, 輸出應(yīng)該為"b"才對(duì),事實(shí)上是怎樣的呢?事實(shí)上輸出了"b",也就是說(shuō)changeReference()方法改變了傳入引用所指對(duì)象的值。
觀點(diǎn)1和觀點(diǎn)2的輸出結(jié)果多少會(huì)讓人有些困惑,別急,我們繼續(xù)往下看。
深入分析
為了詳細(xì)分析這個(gè)問(wèn)題,把上述兩段代碼合起來(lái):
- public class Main {
- public static void modifyReference(Foo cRef){
- cRef.setAttribute("c"); // line DDD
- }
- public static void changeReference(Foo aRef){
- Foo bRef = new Foo("b"); // line FFF
- aRef = bRef; // line EEE
- }
- public static void main(String[] args) {
- Foo fooRef = new Foo("a"); // line AAA
- changeReference(fooRef); // line BBB
- System.out.println(fooRef.getAttribute()); // 輸出 a
- modifyReference(fooRef); // line CCC
- System.out.println(fooRef.getAttribute()); // 輸出 c
- }
- }
下面來(lái)深入內(nèi)部來(lái)詳細(xì)分析一下引用和Object內(nèi)部的變化。來(lái)看下面圖示:
① Line AAA, 申明一個(gè)名叫fooRef,類型為Foo的引用,并見(jiàn)其分配給一個(gè)新的包含屬性值為"f"的對(duì)象,該對(duì)象類型為Foo。
- Foo fooRef = new Foo("a"); // line AAA
② Line DDD, 方法內(nèi)部,申明了一個(gè)Foo類型的名為aRef的引用,且aRef被初始化為null。
- void changeReference(Foo a);
③ Line CCC, changeReference()方法被調(diào)用后,引用aRef被分配給fooRef指向的對(duì)象。
- changeReference(fooRef);
④ Line FFF, 申明一個(gè)名叫bRef,類型為Foo的引用,并見(jiàn)其分配給一個(gè)新的包含屬性值為"b"的對(duì)象,該對(duì)象類型為Foo。
- Foo bRef = new Foo("b");
⑤ Line EEE, 將引用aRef重新分配給了包含屬性"b"的對(duì)象。此處注意,并非將fooRef重新分配,而是aRef。
- aRef = bRef;
⑥ Line CCC, 調(diào)用方法modifyReference(Foo cRef)后,新建了一個(gè)引用cRef并將之分配到包含該屬性"f"的對(duì)象上,該對(duì)象同時(shí)被兩個(gè)引用fooRef和cRef指向著。
- modifyReference(fooRef);
⑦ Line DDD, cRef.setAttribute("c");將會(huì)改變cRef引用指向的包含屬性"f"的對(duì)象,而該對(duì)象同時(shí)被引用fooRef指向著。
- cRef.setAttribute("c");
此時(shí)引用fooRef指向的對(duì)象內(nèi)部屬性值"f"也被重新設(shè)置為"c"。
總結(jié)
Java內(nèi)部方法傳參不是引用傳遞,而是引用本身的"值"的傳遞,歸根結(jié)底還是值傳遞。將一個(gè)對(duì)象的引用fooRef傳給方法的形參newRef,將給該對(duì)象新增了一個(gè)引用,相當(dāng)于多了一個(gè)alias。我們可以通過(guò)這個(gè)原引用fooRef,或這是方法參數(shù)里的新引用newRef去訪問(wèn)、操作原對(duì)象,也可以改變參數(shù)里的引用newRef本身的值,卻無(wú)法改變?cè)胒ooRef的值。