當參數型別為物件(類別、介面、陣列)時,傳遞的是參考的副本(即記憶體位址值)。這導致兩個關鍵行為:
list.add() 等,**影響原物件**。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) ← 毫無影響
void foo(int* p) { *p = 5; } 傳遞位址,可修改原變數。| 需求 | 錯誤寫法 | 正確寫法 |
|---|---|---|
| 方法內需修改呼叫端的集合內容 | 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]。add/put/remove 修改內容,不可替換集合物件本身。+、++、setter)實為產生新物件,原參考不變。
// 罕見但合法:必須在方法內替換呼叫端參考時
void swapWrapper(Object[] holder) {
holder[0] = new Object(); // 修改陣列元素,呼叫端可見
}
// 呼叫端
Object[] holder = { oldObj };
swapWrapper(holder);
// holder[0] 現在是新物件
警告:這屬於「聰明的壞代碼」,降低可讀性。**首選回傳值、回傳封裝物件、或使用 AtomicReference**。
一句話總結:Java 傳物件是「遞交遙控器複印本」——能按按鈕(修改狀態),不能換遙控器(替換參考);不可變類別連按鈕都沒有(操作產生新物件)。