Java深入學習系列之值傳遞Or引用傳遞?
我們來看一個新手甚至寫了多年Java的朋友都可能不是十分確定的問題:
在Java方法傳參時,究竟是引用傳遞還是值傳遞?
為了說明問題, 我給出一個非常簡單的class定義:
- public class Foo {
- String attribute;
- Foo(String s) {
- this.attribute = s;
- }
- void setAttribute(String s) {
- this.attribute = s;
- }
- String getAttribute() {
- return this.attribute;
- }
- }
下面在闡明觀點時,可能會多次用到該類。
關于Java里值傳遞還是引用傳遞,至少從表現形式上來看,兩種觀點都有支撐的論據。下面我來一一分析:
觀點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
- }
- }
上述示例,輸出結果為"c",而不是"c"。
我們在line AAA處新創建了一個Object Foo并將其引用fooRef在line BBB處傳給了方法modifyReference()的參數cRef, 該方法內部處理后,fooRef指向的Object中的值從"a"變成了"c", 而引用fooRef還是那個引用, 因此,我們是否可以認為,在line BBB處發生了引用傳遞?
先留著疑問,我們繼續往下看。
觀點2:值傳遞
繼續看一段代碼
- 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"。
我們在line AAA處新創建了一個Object Foo并將其引用fooRef在line EEE處傳給了方法changeReference()的參數aRef, 該方法內部引用aRef在line DDD處被重新賦值。如果是引用傳遞,那么引用aRef在line EEE處已經被指向了新的Object, 輸出應該為"b"才對,事實上是怎樣的呢?事實上輸出了"b",也就是說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
② Line DDD, 方法內部,申明了一個Foo類型的名為aRef的引用,且aRef被初始化為null。
- void changeReference(Foo a);
③ Line CCC, changeReference()方法被調用后,引用aRef被分配給fooRef指向的對象。
- changeReference(fooRef);
④ Line FFF, 申明一個名叫bRef,類型為Foo的引用,并見其分配給一個新的包含屬性值為"b"的對象,該對象類型為Foo。
- Foo bRef = new Foo("b");
⑤ Line EEE, 將引用aRef重新分配給了包含屬性"b"的對象。此處注意,并非將fooRef重新分配,而是aRef。
- aRef = bRef;
⑥ Line CCC, 調用方法modifyReference(Foo cRef)后,新建了一個引用cRef并將之分配到包含該屬性"f"的對象上,該對象同時被兩個引用fooRef和cRef指向著。
- modifyReference(fooRef);
⑦ Line DDD, cRef.setAttribute("c");將會改變cRef引用指向的包含屬性"f"的對象,而該對象同時被引用fooRef指向著。
- cRef.setAttribute("c");
此時引用fooRef指向的對象內部屬性值"f"也被重新設置為"c"。
總結
Java內部方法傳參不是引用傳遞,而是引用本身的"值"的傳遞,歸根結底還是值傳遞。將一個對象的引用fooRef傳給方法的形參newRef,將給該對象新增了一個引用,相當于多了一個alias。我們可以通過這個原引用fooRef,或這是方法參數里的新引用newRef去訪問、操作原對象,也可以改變參數里的引用newRef本身的值,卻無法改變原引用fooRef的值。