「Java 是值傳遞還是引用傳遞?」這是面試必考、開發易踩的經典話題。結論先行:Java 只有值傳遞。本文通過內存模型圖解與代碼實證,徹底釐清基本類型、對象引用、不可變對象三類場景的傳遞本質。
public class Person {
private String name;
public Person(String name) { this.name = name; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
public class ParamTest {
public static void changeName1(Person p) { // 修改對象內部狀態
p.setName("Bob");
}
public static void changeName2(Person p) { // 重新賦值參數
Person another = new Person("Tom");
p = another; // 僅改變局部引用指向
}
public static void main(String[] args) {
Person person = new Person("Lily"); // 地址 0x60
changeName1(person); // 0x60 內 name → "Bob"
System.out.println(person.getName()); // Bob ✅
changeName2(person); // 參數 p 複製 0x60,內部 p 指向 0x65
System.out.println(person.getName()); // Bob ✅ 未變
}
}
main 棧幀 changeName2 棧幀
┌──────────────────┐ ┌──────────────────┐
│ person ──┐ │ │ p (參數) ──┐ │
└──────────│───────┘ └────────────│─────┘
│ │
▼ ▼ (賦值後)
┌─────────┐ ┌─────────┐
0x60 │ name="Bob"│ 0x65 │ name="Tom"│
└─────────┘ └─────────┘
關鍵:p = another 只是將棧幀局部變量 p 指向新地址 0x65,主方法的 person 仍指向 0x60,互不干擾。
public static void testString(String s) {
s = "changed"; // 重新賦值,創建新字符串對象
}
public static void testInteger(Integer i) {
i = 200; // 自動拆箱/裝箱生成新 Integer 對象
}
因為不可變類無任何修改內部狀態的方法,所以外部感知上「像值傳遞」,本質仍是引用地址值傳遞。
「Java 嚴格採用按值調用。方法接收的是實參值的副本:基本類型複製數值,引用類型複製對象地址。因此:這設計在保證簡單性的同時,避免了 C++ 指針/引用的複雜性與風險。」
- 方法內重新賦值參數(指向新對象)不影響外部;
- 方法內通過引用修改對象可變狀態(如 setter、集合操作)會影響外部共享對象;
- 對於不可變對象,無修改狀態手段,行為等同於值傳遞。
AtomicReference、單元素數組 T[]);Collections.unmodifiableList();「參數傳遞看似微觀,實則關繫系統邊界的契約設計。守住『不隨意修改入參狀態』原則,微服務介面也會更乾淨。」