「Java 到底是值傳遞還是引用傳遞?」這是許多 Java 學習者甚至資深開發者常爭論的話題。網路上眾說紛紜,有的說兩者皆有,有的堅稱只有值傳遞。本文將從 JVM 記憶體模型與賦值操作本質出發,結合程式碼實證,徹底釐清這個概念。
方法參數為基本資料類型(int、double、boolean 等)時,呼叫方法會將實際參數的值的副本傳給形式參數。兩者在記憶體中佔據不同位置,方法內修改形式參數不影響實際參數。
方法參數為參考類型(物件、陣列)時,傳遞的是物件在堆記憶體中的記憶體位址(引用)的副本。實際參數與形式參數指向同一個物件,方法內透過形式參數修改物件內部狀態(如欄位值)會影響原物件,但重新指派形式參數指向新物件則不影響實際參數。
public class TestPrimitive {
public static void main(String[] args) {
int a = 10;
changeNum(a);
System.out.println("main a = " + a); // 輸出 10
}
static void changeNum(int a) {
a = 100;
System.out.println("changeNum a = " + a); // 輸出 100
}
}
結果解析:main 中的 a 仍為 10。證明基本類型傳遞的是值的副本,方法內操作的是獨立變數。
class Person {
String name;
Person(String name) { this.name = name; }
@Override public String toString() { return name; }
}
public class TestRefModify {
public static void main(String[] args) {
Person p = new Person("Alice");
System.out.println("before: " + p); // Alice
modifyName(p);
System.out.println("after: " + p); // Bob
}
static void modifyName(Person p) {
p.name = "Bob"; // 修改物件內部欄位
}
}
結果解析:main 中的 p 變為 "Bob"。因為形式參數 p 複製了引用值,指向同一個堆上物件,透過引用修改欄位會影響原物件。
public class TestRefReassign {
static Person p1 = new Person("p1");
public static void main(String[] args) {
Person p = new Person("P");
System.out.println("before: " + p); // P
changeObj(p);
System.out.println("after: " + p); // P (未變!)
}
static void changeObj(Person p) {
p = new Person("pp"); // 形式參數指向新物件
System.out.println("in method: " + p); // pp
// p = p1; // 同理,指向靜態物件也不影響外部
}
}
結果解析:main 中的 p 仍指向 "P"。方法內 p = new Person(...) 只是將區域變數(形式參數)的引用指向新物件,並未改變外部實際參數的引用值。
a = b 直接複製記憶體中的位元值。refA = refB 複製的是記憶體位址,而非物件本身。兩個引用指向同一物件,但引用變數本身是獨立的棧上變數。

圖解說明:str = "world" 只是讓引用 str 指向新字串物件 "world",原 "hello" 物件不受影響(若無其他引用將被 GC 回收)。
呼叫方法時,實際參數賦值給形式參數,本質就是一次賦值:


圖解說明:方法內 p = new Person("pp") 只改變區域引用 p 的指向,外部引用仍指向原物件。
Java 只有值傳遞。
- 基本類型傳遞的是資料值的副本。
- 參考類型傳遞的是引用(記憶體位址)的副本。
方法參數在方法內部是區域變數,其作用域與生命週期僅限於方法執行期間。理解「引用的值被複製」這一點,就能避開所有關於參數傳遞的誤區。
list.add() vs list = new ArrayList() 差異? 前者修改共享物件內容,外部可見;後者僅改變區域引用指向,外部不受影響。掌握此核心機制,不僅能通過面試,更能在日常開發中避免因誤解參數傳遞而導致的難以排查的 Bug。