首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 方法參數傳遞物件參考的核心陷阱:參考傳值的真相與實證

     編輯:Java 相關知識

核心結論:Java 只有「值傳遞」

當參數型別為物件(類別、介面、陣列)時,傳遞的是參考的副本(即記憶體位址值)。這導致兩個關鍵行為:

  1. 可修改物件內部狀態:透過參考副本呼叫 setter、修改欄位、呼叫 list.add() 等,**影響原物件**。
  2. 無法替換呼叫端的參考:在方法內將參數指向 new Object(),**不影響**呼叫端原變數指向的物件。

完整可執行實證程式碼


public class ObjectParameterTrap {

    public static void main(String[] args) {
        // 測試 1:可變物件
        System.out.println("=== 可變物件測試 ===");
        MutableWrapper mw = new MutableWrapper();
        mw.value = 10;
        System.out.println("呼叫前: " + mw.value); // 10
        modifyInternal(mw);
        System.out.println("呼叫後: " + mw.value); // 20 ✅ 內部被修改
        replaceReference(mw);
        System.out.println("替換後: " + mw.value); // 20 ❌ 未被替換

        // 測試 2:不可變物件
        System.out.println("\n=== 不可變物件測試 ===");
        Integer num = 10;
        System.out.println("呼叫前: " + num); // 10
        tryModifyInteger(num);
        System.out.println("呼叫後: " + num); // 10 ❌ Integer 不可變,自動拆箱/裝箱產生新物件
        replaceInteger(num);
        System.out.println("替換後: " + num); // 10 ❌ 參考替換無效
    }

    // 修改物件內部狀態(生效)
    static void modifyInternal(MutableWrapper obj) {
        obj.value = 20; // 透過參考副本修改同一物件
    }

    // 嘗試替換參考(無效)
    static void replaceReference(MutableWrapper obj) {
        obj = new MutableWrapper(); // 區域參考指向新物件
        obj.value = 99;
    }

    // Integer 不可變,任何「修改」實為產生新物件
    static void tryModifyInteger(Integer num) {
        num = 20; // 自動裝箱:num = Integer.valueOf(20),區域參考指向新物件
    }

    static void replaceInteger(Integer num) {
        num = Integer.valueOf(99); // 同理
    }

    static class MutableWrapper {
        int value;
    }
}

執行結果與記憶體圖解

=== 可變物件測試 ===
呼叫前: 10
呼叫後: 20      ← 內部修改生效
替換後: 20      ← 參考替換無效

=== 不可變物件測試 ===
呼叫前: 10
呼叫後: 10      ← 拆箱/裝箱產生新物件,原參考不變
替換後: 10      ← 參考替換無效

記憶體模型示意

呼叫端:  mw ──────► [MutableWrapper@100] (value=10)
                │
                ▼ 傳遞參考值副本
方法內:  obj ─────► [MutableWrapper@100] (value=20)  ← modifyInternal 修改內容
                │
                ▼ obj = new MutableWrapper()
方法內:  obj ─────► [MutableWrapper@200] (value=99)  ← 區域參考指向新物件
呼叫端:  mw ──────► [MutableWrapper@100] (value=20)  ← 毫無影響

為什麼會有「引用傳遞」的誤解?

  • C/C++ 使用指標模擬引用傳遞:void foo(int* p) { *p = 5; } 傳遞位址,可修改原變數。
  • Java 移除指標,但保留參考。參考本質是「物件的遙控器」,傳遞的是「遙控器的複印本」。
  • 兩個遙控器控制同一台電視(物件),但換掉手中的遙控器(參考重新指向),不會換掉別人手中的遙控器。

實務防禦性程式設計指南

需求錯誤寫法正確寫法
方法內需修改呼叫端的集合內容void add(List list) { list = new ArrayList(); list.add(x); }void add(List list) { list.add(x); } 或回傳新集合
方法內需建立新物件並回傳void create(User user) { user = new User(); }User create() { return new User(); }
避免外部竄改內部集合class Foo { List items; void setItems(List l) { items = l; } }void setItems(List l) { items = new ArrayList(l); } (防禦性複製)
建構子/Setter 接收可變物件直接指派防禦性複製:this.list = new ArrayList(inputList);

特殊情況:陣列與集合

  • 陣列:參考傳值,可修改元素 arr[0]=5,不可替換陣列本身 arr = new int[10]。
  • Collection/Map:參考傳值,可 add/put/remove 修改內容,不可替換集合物件本身。
  • String/Integer/Long 等不可變類別:任何「修改」操作(+、++、setter)實為產生新物件,原參考不變。

進階:利用單元素陣列/容器模擬「輸出參數」


// 罕見但合法:必須在方法內替換呼叫端參考時
void swapWrapper(Object[] holder) {
    holder[0] = new Object(); // 修改陣列元素,呼叫端可見
}

// 呼叫端
Object[] holder = { oldObj };
swapWrapper(holder);
// holder[0] 現在是新物件

警告:這屬於「聰明的壞代碼」,降低可讀性。**首選回傳值、回傳封裝物件、或使用 AtomicReference**。


一句話總結:Java 傳物件是「遞交遙控器複印本」——能按按鈕(修改狀態),不能換遙控器(替換參考);不可變類別連按鈕都沒有(操作產生新物件)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved