「Java 只有值傳遞」是核心語言規範,但引用類型傳遞行為常被誤解為引用傳遞。本文從 JVM 內存模型、代碼實驗、字節碼層面徹底釐清機制。
值傳遞(基本類型):實參值複製給形參,兩者獨立內存單元,形參變化不影響實參。
引用傳遞(引用類型):實參為對象/數組,形參與實參指向同一堆內存地址,形參操作即實參操作,結果保留。
引用類型傳遞的是「引用的值」(記憶體地址),而非引用本身。
// 內存圖解
main 棧幀: list ──────┐
▼
堆內存: ArrayList 對象 @0x1234
▲
called 棧幀: param ────┘ (複製了地址值 0x1234)
public void primitiveTest(int num) {
num = 20; // 修改形參
}
public static void main(String[] args) {
int value = 10;
primitiveTest(value);
System.out.println(value); // 輸出 10,未變
}
public void modifyList(List list) {
list.add("new"); // 修改對象內容
}
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("old");
modifyList(list);
System.out.println(list); // 輸出 [old, new] - 對象內容變了!
}
解析:傳遞的是引用地址副本,雙方指向同一對象,通過副本調用方法修改對象內部狀態,對原引用可見。
public void reassignList(List list) {
list = new ArrayList<>(); // 形參指向新對象
list.add("new");
}
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("old");
reassignList(list);
System.out.println(list); // 輸出 [old] - 原引用未變!
}
解析:形參 list 是實參引用地址的副本,list = new ... 只是讓副本指向新對象,原引用仍指向舊對象。
List list1 = new ArrayList<>();
list1.add(new Bean("A"));
list1.add(new Bean("B"));
List list2 = new ArrayList<>();
list2.addAll(list1); // 複製引用,而非複製對象!
list2.get(0).setName("Modified");
System.out.println(list1.get(0).getName()); // 輸出 "Modified" - 共享同一 Bean 對象!
List list2 = new ArrayList<>(list1); // 或 list2.addAll(list1)
// 修改 list2 內容(增刪元素)不影響 list1
// 但修改元素對象內部狀態會相互影響
// 手動深拷貝
List list2 = new ArrayList<>();
for (Bean b : list1) {
Bean copy = new Bean();
copy.setName(b.getName());
copy.setAge(b.getAge());
list2.add(copy);
}
// 或使用序列化深拷貝 (實現 Serializable)
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(list1);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
List list2 = (List) ois.readObject();
// Bean 提供複製建構子
public Bean(Bean other) {
this.name = other.name;
this.age = other.age;
}
// 使用 Stream API
List list2 = list1.stream()
.map(Bean::new) // 假設有複製建構子
.collect(Collectors.toList());
| 區域 | 存儲內容 | 傳遞行為 |
|---|---|---|
| 棧 (Stack) | 基本類型值、引用地址 | 逐值複製 |
| 堆 (Heap) | 對象實例、數組 | 共享引用指向同一對象 |
| 方法區 | 類元數據、常量池 | 不直接參與傳遞 |
process(List items) 暗示只讀,modify(List items) 暗示可變this.list = new ArrayList<>(list)String、LocalDate、Guava ImmutableList 等不可變類型Collections.unmodifiableList(list) 返回只讀視圖防範外部修改@param list 不可為 null,調用者保證不再修改理解「引用的值傳遞」是 Java 開發者進階關鍵。牢記:棧上複製地址,堆上共享對象。善用不可變對象、防禦性複製、只讀視圖,從源頭消除共享可變狀態帶來的隱患。