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

Java 參數傳遞機制深度解析:為什麼說「只有傳值」,物件引用傳遞陷阱全解

     編輯:Java 相關知識

核心結論:Java 唯一參數傳遞模式 = **傳值**

  • 基本型別:複製 **位元值** (bit pattern)。
  • 參考型別:複製 **參考值** (記憶體位址/句柄),**不複製物件本身**。

方法收到的是**原始變數的副本**。對副本重新賦值 (指向新物件) **不影響** 原變數;但透過副本**修改物件內部狀態** 則**影響共享物件**。

圖解記憶體模型


呼叫端:
  Dog d = new Dog("Max");  // d ──► [Dog@100: name="Max"]

呼叫方法: void rename(Dog dog) { ... }

方法執行時:
  參數 dog (副本) ──► [Dog@100: name="Max"] ◄── 原始 d
  同一物件,兩個參考變數指向它

三大經典陷阱實測

陷阱 1:嘗試在方法內「換掉」呼叫端的物件


void swapRef(Dog dog) {
    dog = new Dog("Buddy"); // 參數 dog 指向新物件
    // 原始 d 仍指向 "Max"
}

**結果**:呼叫端 `d` 不變。參數 `dog` 是區域變數,重新賦值只改變區域副本指向。

陷阱 2:修改物件內部狀態 — 這次生效了


void changeName(Dog dog) {
    dog.setName("Buddy"); // 透過副本修改共享物件
}

**結果**:呼叫端 `d.getName()` 變為 `"Buddy"`。兩個參考指向**同一物件**,修改內容當然可見。

陷阱 3:不可變物件 — 看似修改實則新建


void modifyString(String s) {
    s = s + " World"; // 字串不可變,實際產生新 String 物件
    // 參數 s 指向新物件,原始字串不變
}

**結果**:呼叫端字串不變。`String`、`Integer`、`BigDecimal` 等不可變類別,「修改」操作實為**建立新物件並讓區域參考指向它**,原物件不變。

完整可執行驗證代碼


class Dog { String name; Dog(String n){name=n;} void setName(String n){name=n;} }

public class PassByValueDemo {
    public static void main(String[] args) {
        // 1. 基本型別
        int x = 10;
        modifyPrimitive(x);
        System.out.println("基本型別: " + x); // 10

        // 2. 可變物件 - 修改內部狀態
        Dog d1 = new Dog("Max");
        modifyDogState(d1);
        System.out.println("修改狀態: " + d1.name); // Buddy

        // 3. 可變物件 - 嘗試換引用
        Dog d2 = new Dog("Max");
        swapDogRef(d2);
        System.out.println("換引用: " + d2.name); // Max (不變)

        // 4. 不可變物件
        String s = "Hello";
        modifyString(s);
        System.out.println("字串: " + s); // Hello (不變)
    }

    static void modifyPrimitive(int n) { n = 20; }
    static void modifyDogState(Dog d) { d.setName("Buddy"); }
    static void swapDogRef(Dog d) { d = new Dog("Buddy"); }
    static void modifyString(String s) { s = s + " World"; }
}

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

  1. 術語混淆:C++ 有「傳引用」 (`void f(int& x)`) 語法,Java 無此語法。
  2. 現象相似:傳物件參考時,**能修改物件內容**,直覺以為是傳引用。
  3. 教材簡化:部分教材稱「物件傳引用」意指「傳物件引用的值」,易被誤讀。

實務防錯指南

  • 方法不應修改輸入參數指向:若需回傳新物件,用返回值或 `Optional`/`Result` 封裝。
  • 防御性複製:建構子/Setter 接收可變物件 (Date, Collection, 自定義) 時,**複製一份** 儲存;Getter 回傳時也**複製** 或回傳不可變視圖 (`Collections.unmodifiableList`)。
    
    public void setBirthDate(Date d) { this.birthDate = new Date(d.getTime()); }
    public Date getBirthDate() { return new Date(birthDate.getTime()); }
    
  • 偏好不可變類別:`record`、最終類別、無 Setter、欄位 `final`、防御性複製 — 消除別名副作用根源。
  • 集合參數:`void process(List items)` — 方法內 `items.add()` 會影響呼叫端;`items = new ArrayList()` 不影響。傳入 `List.copyOf(items)` 或 `Stream` 避免副作用。

總結

Java **永遠傳值**。理解「參考值副本」與「共享物件」的區別,配合防御性複製、不可變設計、明確的 API 契約 (輸入參數只讀、輸出用返回值),即可徹底避開參數傳遞陷阱,寫出可靠、可推理的代碼。

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