【Java深刻學習系列】之值傳遞Or引用傳遞?

咱們來看一個新手甚至寫了多年Java的朋友均可能不是十分肯定的問題:ide

在Java方法傳參時,到底是引用傳遞仍是值傳遞?

爲了說明問題, 我給出一個很是簡單的class定義:this

public class Foo {
  String attribute;
  Foo(String s) {
    this.attribute = s;
  }
  void setAttribute(String s) {
    this.attribute = s;
  }
  String getAttribute() {
    return this.attribute;
  }
}

下面在闡明觀點時,可能會屢次用到該類。spa

關於Java裏值傳遞仍是引用傳遞,至少從表現形式上來看,兩種觀點都有支撐的論據。下面我來一一分析:3d

觀點1:引用傳遞

理由以下:
先看一段代碼code

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
  }
}

上述示例,輸出結果爲"c",而不是"a", 也就是傳入的fooRef裏的屬性被修改了,發生了side-effect。對象

咱們在line AAA處新建立了一個Object Foo並將其引用fooRefline BBB處傳給了方法modifyReference()的參數cRef, 該方法內部處理後,fooRef指向的Object中的值從"a"變成了"c", 而引用fooRef仍是那個引用, 所以,咱們是否能夠認爲,在line BBB處發生了引用傳遞?blog

先留着疑問,咱們繼續往下看。ip

觀點2:值傳遞

繼續看一段代碼get

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
  }
}

上述示例,輸出結果爲"a", 而不是"b", 即對傳入的fooRef內部的change並無影響外部的傳入前的值。it

咱們在line AAA處新建立了一個Object Foo並將其引用fooRefline EEE處傳給了方法changeReference()的參數aRef, 該方法內部引用aRefline DDD處被從新賦值。若是是引用傳遞,那麼引用aRefline EEE處已經被指向了新的Object, 輸出應該爲"b"纔對,事實上是怎樣的呢?事實上輸出了"a",也就是說changeReference()方法改變了傳入引用所指對象的值。

觀點1和觀點2的輸出結果多少會讓人有些困惑,別急,咱們繼續往下看。

深刻分析

爲了詳細分析這個問題,把上述兩段代碼合起來:

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
    

  }
}

下面來深刻內部來詳細分析一下引用和Object內部的變化。
來看下面圖示:

① Line AAA, 申明一個名叫 fooRef,類型爲 Foo的引用,並見其分配給一個新的包含屬性值爲 "f"的對象,該對象類型爲 Foo
Foo fooRef = new Foo("a"); // line AAA

clipboard.png

② Line DDD, 方法內部,申明瞭一個 Foo類型的名爲 aRef的引用,且 aRef被初始化爲 null
void changeReference(Foo a);

clipboard.png

③ Line CCC, changeReference()方法被調用後,引用 aRef被分配給 fooRef指向的對象。
changeReference(fooRef);

clipboard.png

④ Line FFF, 申明一個名叫 bRef,類型爲 Foo的引用,並見其分配給一個新的包含屬性值爲 "b"的對象,該對象類型爲 Foo
Foo bRef = new Foo("b");

clipboard.png

⑤ Line EEE, 將引用 aRef從新分配給了包含屬性 "b"的對象。此處注意,並不是將 fooRef從新分配,而是 aRef
aRef = bRef;

clipboard.png

⑥ Line CCC, 調用方法 modifyReference(Foo cRef)後,新建了一個引用 cRef並將之分配到包含該屬性 "f"的對象上,該對象同時被兩個引用 fooRefcRef指向着。
modifyReference(fooRef);

clipboard.png

⑦ Line DDD, cRef.setAttribute("c");將會改變 cRef引用指向的包含屬性 "f"的對象,而該對象同時被引用 fooRef指向着。
cRef.setAttribute("c");

clipboard.png

此時引用fooRef指向的對象內部屬性值"f"也被從新設置爲"c"

總結

Java內部方法傳參不是引用傳遞,而是引用自己的"值"的傳遞,歸根結底仍是值傳遞。將一個對象的引用fooRef傳給方法的形參newRef,將給該對象新增了一個引用,至關於多了一個alias。咱們能夠經過這個原引用fooRef,或這是方法參數裏的新引用newRef去訪問、操做原對象,也能夠改變參數裏的引用newRef自己的值,卻沒法改變原引用fooRef的值。

相關文章
相關標籤/搜索