「Java 是傳值還是傳引用?」這是經典面試題,也是無數開發者的困惑來源。本文從記憶體模型、語言語意、編譯器行為三個維度,深度對比 Java、C++、C# 三大語言的參數傳遞機制,徹底釐清傳值、傳引用、指標傳遞的本質區別。
| 語言 | 基本型別 | 物件/類別型別 | 備註 |
|---|---|---|---|
| Java | 傳值 (複製值) | 傳值 (複製引用/指標) | 無傳引用機制,引用本身不可修改 |
| C++ | 傳值 (預設) | 傳值 (預設,複製物件) | 支援真正傳引用 (T&) 與指標傳遞 (T*) |
| C# | 傳值 (結構體) | 傳引用 (類別) | ref/out 強制傳引用 |
void primitive(int x) { x = 10; }
int a = 5;
primitive(a); // a 仍為 5,x 是 a 的位元複製
class Box { int value; }
void modifyRef(Box b) {
b.value = 20; // ✅ 修改物件內部狀態:透過複製的引用找到同一物件
b = new Box(); // ❌ 修改區域引用:b 指向新物件,外部原引用不變
b.value = 30; // 只影響新物件
}
Box original = new Box();
original.value = 5;
modifyRef(original);
System.out.println(original.value); // 20 (內部狀態被修改)
呼叫前: 呼叫 modifyRef 時:
main 棧框 modifyRef 棧框
┌─────────┐ ┌─────────┐
│ original│ ─────────────► │ b │ (複製引用值,指向同一物件)
└─────────┘ └─────────┘
│ │
▼ ▼
┌─────────────────────────────────────────┐
│ Heap │
│ ┌──────────────┐ │
│ │ Box@0x1234 │ ◄─────────────────────┘
│ │ value = 5 │
│ └──────────────┘
└─────────────────────────────────────────┘
#include
void byValue(int x) { x = x + 1; }
void byRef(int& x) { x = x + 1; } // 真正傳引用:別名
void byPointer(int* x) { *x = *x + 1; } // 指標傳遞:傳遞位址
int main() {
int v = 1;
byValue(v); // v = 1 (複製值)
byRef(v); // v = 2 (別名修改)
byPointer(&v); // v = 3 (解引用修改)
int* vp = &v;
byPointer(vp); // v = 4 (傳遞指標副本,但解引用修改同一記憶體)
std::cout << v; // 4
}
byRef(int& x):x 是 v 的別名,編譯器層面綁定,修改 x 即修改 vbyPointer(int* x):x 是指標副本 (傳值),但 *x 解引用修改 vbyPointer(&v) (傳址/引用語意) vs byPointer(vp) (傳指標值)
struct Point { public int X, Y; } // 值類型
class Node { public int Value; } // 參考類型
void val(Point p) { p.X = 10; } // 複製結構體
void refVal(ref Point p) { p.X = 10; } // 傳引用 (ref)
void cls(Node n) { n.Value = 10; } // 複製引用 (同 Java)
void refCls(ref Node n) { n = new Node(); } // 傳引用可修改引用本身
| 語言 | String 類型 | 傳遞行為 | 不可變性 |
|---|---|---|---|
| Java | 類別 (參考型別) | 傳值 (複製引用) | 不可變,修改產生新物件 |
| C# | 類別 (參考型別) | 傳引用 (複製引用) | 不可變,修改產生新物件 |
兩者行為一致:void f(String s) { s = "new"; } 不影響外部原字串,因為 s 是複製的引用,重新指向新物件。
判斷傳值還是傳引用,看記憶體發生什麼:
• 複製記憶體內容 → 傳值 (含複製指標/引用)
• 共享記憶體位址 → 傳引用 (別名綁定)
• 指標:可視為「可傳值、可傳引用」的雙重身分,但解引用行為為傳引用語意
T& 避免大物件拷貝、T* 可選參數/輸出參數、const T& 唯讀避免拷貝ref/out 明確表達修改引用意圖,結構體預設傳值需注意效能