「Java 到底是值傳遞還是引用傳遞?」這是面試必考、開發必懂的核心知識點。本文從 JVM 內存模型出發,徹底講清基本類型、String、數組、普通對象的傳遞行為,揭示「引用傳值」的本質,並給出工程中修改參數、保護不可變性、多返回值的最佳實踐模式。
Java 永遠是值傳遞 (Call by Value)。 對於引用類型,傳遞的是「引用的值」(即對象在堆上的地址副本),而非對象本身,也非引用別名。
| 類別 | 示例 | 傳遞內容 | 方法內修改參數變量 | 方法內修改對象內容 |
|---|---|---|---|---|
| 基本類型 | int, double, boolean | 數值副本 | 不影響外部 | 不適用 |
| String | String s = "abc" | 引用值副本 | 不影響外部 (重新賦值指向新對象) | 不可修改 (String 不可變) |
| 數組 | int[] arr = {1,2} | 引用值副本 | 不影響外部 | 影響外部 (同一堆對象) |
| 普通對象 | User u = new User() | 引用值副本 | 不影響外部 | 影響外部 (同一堆對象) |
public class ParamPassingTest {
// 基本類型
static void t1(int n) { n = 10; }
// String (不可變對象)
static void t2(String s) { s = "123"; }
// 數組 (可變對象)
static void t3(int[] arr) { arr[0] = 2; }
// 普通對象
static void t4(User u) { u.setName("Bob"); }
static void t5(User u) { u = new User("Alice"); } // 重新賦值參數
public static void main(String[] args) {
int m = 5; t1(m); System.out.println(m); // 5
String s = "abc"; t2(s); System.out.println(s); // abc
int[] a = {1,2,3}; t3(a); System.out.println(a[0]); // 2
User u = new User("Tom"); t4(u); System.out.println(u.getName()); // Bob
User v = new User("Tom"); t5(v); System.out.println(v.getName()); // Tom (未變)
}
}
class User { String name; User(String n){name=n;} void setName(String n){name=n;} String getName(){return name;} }
調用前:main 棧幀 堆
v ──────────────────► User("Tom") @0x100
調用 t5(v) 時:棧幀 t5
u (參數) ──────────► User("Tom") @0x100 (複製引用值)
執行 u = new User("Alice"):
u ────────────────► User("Alice") @0x200 (參數指向新對象)
v 仍指向 @0x100,完全不受影響
關鍵認知:參數 u 是方法棧幀中的局部變量,存儲引用值。賦值新對象只改變局部變量指向,斷不了調用方變量 v 的連線。
// ❌ 無效
void inc(int n) { n++; }
// ✅ 有效
int inc(int n) { return n + 1; }
// 調用:m = inc(m);
// ❌ 直接修改傳入對象,副作用不可控
void process(List list) { list.add("x"); }
// ✅ 內部拷貝,隔離副作用
void process(List list) {
List copy = new ArrayList<>(list); // 淺拷貝
copy.add("x");
// 返回 copy 或繼續處理
}
// ✅ 不可變集合 (Java 9+)
List safe = List.copyOf(list); // 不可變視圖
// 方案 A: 封裝 Result 對象 (推薦,語義清晰)
class ParseResult { boolean success; int value; String error; }
ParseResult parse(String s) { ... }
// 方案 B: 數組/集合作為「輸出參數」模擬引用傳遞
void initArray(int[] arr) { for (int i=0;i
String 是 不可變對象 + 常量池優化 的雙重特性:
String s = "abc"; → 常量池String s = new String("abc"); → 堆新對象concat, replace, substring) 均返回 新 String 對象s = s + "x"; 絕不影響外部引用理解「引用傳值」本質,是寫出無副作用 Bug、可維護 Java 代碼的基石。下次面試或 Code Review 遇到參數傳遞問題,直接甩出這張表,專業又高效。