Java 參數傳遞機制鐵律
Java 唯一支援「值傳遞」:方法接收的是所有參數值的副本,無法修改呼叫端變數本身的引用/值。
兩類參數傳遞行為對比
| 參數類型 | 傳遞內容 | 方法內可否修改呼叫端變數 | 方法內可否修改物件狀態 |
| 基本型別 | 值的副本 | 不可 | 不適用 |
| 物件引用 | 引用的副本(指向同一物件) | 不可(無法讓引用指向新物件) | 可(透過引用修改物件內部狀態) |
驗證程式碼:物件引用值傳遞
class MyClass {
private Object value;
public MyClass(Object v) { this.value = v; }
public Object getValue() { return value; }
public void setValue(Object v) { this.value = v; }
}
public class ParamPassingDemo {
// 修改物件狀態:成功
static void changeState(MyClass obj) {
obj.setValue(100); // 修改同一物件的內部欄位
}
// 嘗試交換引用:失敗
static void swapRef(MyClass a, MyClass b) {
MyClass temp = a;
a = b; // 僅修改區域變數 a 的引用
b = temp; // 呼叫端原始變數完全不受影響
}
public static void main(String[] args) {
MyClass x = new MyClass(10);
MyClass y = new MyClass(100);
System.out.println("呼叫 changeState 前: x=" + x.getValue() + ", y=" + y.getValue());
changeState(x);
System.out.println("呼叫 changeState 後: x=" + x.getValue() + ", y=" + y.getValue()); // x 變 100
System.out.println("呼叫 swapRef 前: x=" + x.getValue() + ", y=" + y.getValue());
swapRef(x, y);
System.out.println("呼叫 swapRef 後: x=" + x.getValue() + ", y=" + y.getValue()); // 無變化
}
}
三種正確交換變數值的方案
方案一:修改物件內部狀態(適用可變物件)
static void swapState(MyClass a, MyClass b) {
Object temp = a.getValue();
a.setValue(b.getValue());
b.setValue(temp);
}
// 呼叫端:x、y 引用不變,但內部 value 已交換
方案二:使用陣列/容器包裝(模擬引用傳遞)
static void swapWrapper(MyClass[] arr) {
MyClass temp = arr[0];
arr[0] = arr[1];
arr[1] = temp;
}
// 呼叫端:
MyClass[] wrapper = {x, y};
swapWrapper(wrapper);
x = wrapper[0];
y = wrapper[1];
方案三:返回新物件/結果物件(函數式風格)
// 返回交換後的陣列
static MyClass[] swapReturn(MyClass a, MyClass b) {
return new MyClass[]{b, a};
}
// 呼叫端:
MyClass[] swapped = swapReturn(x, y);
x = swapped[0];
y = swapped[1];
方案四:基本型別交換(需返回結果)
// 錯誤:無法交換基本型別
static void swapPrimitive(int a, int b) { int t=a; a=b; b=t; }
// 正確:返回交換結果
static int[] swapPrimitiveReturn(int a, int b) {
return new int[]{b, a};
}
// 或使用 AtomicInteger 等包裝類
static void swapAtomic(AtomicInteger a, AtomicInteger b) {
int temp = a.get();
a.set(b.get());
b.set(temp);
}
記憶體模型圖解
// 呼叫端棧幀 方法棧幀 堆疊
// ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
// │ x ──────────┼─────────────►│ objRef ─────┼─────────────►│ MyClass@100 │
// │ │ 值傳遞副本 │ │ 同一引用 │ value: 10 │
// │ y ──────────┼─────────────►│ objRef2 ────┼─────────────►│ MyClass@200 │
// └─────────────┘ └─────────────┘ │ value: 100 │
// └─────────────┘
// swapRef 內部:
// objRef ← objRef2 (僅區域變數指向改變,呼叫端 x、y 完全不受影響)
最佳實踐建議
- 理解值傳遞本質:不嘗試在方法中修改呼叫端變數引用
- 優先用不可變物件:避免狀態修改帶來的副作用
- 需交換時返回結果:函數式風格
return new Pair(b, a) 最清晰
- 可變物件謹慎共享:明確文檔標註方法是否修改參數物件狀態
- 基本型別交換:直接返回陣列/容器,或使用
AtomicInteger 等包裝類
常見面試陷阱題
| 程式碼片段 | 輸出結果 | 解析 |
| swap(int a, int b) { int t=a; a=b; b=t; } | 無效果 | 基本型別值傳遞 |
| swap(Integer a, Integer b) { ... } | 無效果 | Integer 不可變,引用值傳遞 |
| swap(StringBuilder a, StringBuilder b) { a.append(b); b.append(a); } | 修改內容 | 可變物件,修改內部狀態 |
| swapObj(MyObj a, MyObj b) { a=b; } | 無效果 | 引用副本重新指向 |