Java 參數傳遞永遠是值傳遞 (Call by Value)。無例外。
public class PrimitivePassByValue {
public static void modify(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("方法內: a=" + a + ", b=" + b); // 交換成功
}
public static void main(String[] args) {
int x = 10, y = 20;
System.out.println("調用前: x=" + x + ", y=" + y); // 10, 20
modify(x, y);
System.out.println("調用後: x=" + x + ", y=" + y); // 仍為 10, 20 (未變)
}
}
記憶體圖解:棧幀中基本類型區域變數獨立,修改只影響副本。
class Book {
String title;
Book(String t) { this.title = t; }
}
public class ReferencePassByValue {
// 1. 修改物件內部狀態:生效
static void changeTitle(Book book, String newTitle) {
book.title = newTitle; // 通過引用副本修改同一物件
}
// 2. 嘗試改變引用指向:無效
static void reassign(Book book) {
book = new Book("新書"); // 只改變局部引用副本指向
}
// 3. 交換引用:無效
static void swap(Book a, Book b) {
Book temp = a;
a = b;
b = temp; // 只交換局部引用副本
}
public static void main(String[] args) {
Book b1 = new Book("Java核心技術");
Book b2 = new Book("Effective Java");
System.out.println("調用前: b1=" + b1.title + ", b2=" + b2.title);
changeTitle(b1, "Java編程思想");
System.out.println("changeTitle後: b1=" + b1.title); // 生效:Java編程思想
reassign(b1);
System.out.println("reassign後: b1=" + b1.title); // 無效:仍為 Java編程思想
swap(b1, b2);
System.out.println("swap後: b1=" + b1.title + ", b2=" + b2.title); // 無效:未交換
}
}
public class ImmutableTrap {
static void modifyString(String s) {
s = s + " modified"; // 字串拼接產生新物件,s 指向新物件
}
static void modifyInteger(Integer i) {
i = i + 1; // 自動拆箱→運算→自動裝箱產生新 Integer,i 指向新物件
}
static void modifyList(List list) {
list.add("new"); // 修改列表內容:生效 (列表可變)
// list = new ArrayList<>(); // 重新賦值:無效
}
public static void main(String[] args) {
String str = "original";
modifyString(str);
System.out.println(str); // "original" (未變)
Integer num = 10;
modifyInteger(num);
System.out.println(num); // 10 (未變)
List list = new ArrayList<>(Arrays.asList("a"));
modifyList(list);
System.out.println(list); // [a, new] (生效)
}
}
核心:不可變物件的「修改」實為建立新物件,原引用仍指向舊物件。
String process(String input) {
return input.trim().toUpperCase(); // 返回新字串
}
// 調用: str = process(str);
// 通用持有者
class Holder { T value; }
// 或使用陣列 (長度 1)
String[] holder = { "initial" };
void modify(String[] h) { h[0] = "modified"; }
// 調用: modify(holder); // holder[0] 變為 "modified"
void populate(Map map) {
map.put("key", "value"); // 修改 Map 內容
}
void addItems(List- list, Item... items) {
Collections.addAll(list, items); // 修改 List 內容
}
| 場景 | 建議 | 範例 |
|---|---|---|
| 輸入參數 | 基本類型、不可變物件、介面類型 | void process(String id, List |
| 輸出參數 | 返回值、可變容器參數、Builder 模式 | Result process(Input in) 或 void fill(Result out) |
| 大量參數 | 參數物件 (Parameter Object) / Builder | void query(QueryParams params) |
| 可選參數 | 重載、Builder、Optional、Map | void config(ConfigBuilder b) |
| 線程安全 | 不可變物件、防禦性複製、ThreadLocal | void process(ImmutableData data) |
public final class Period {
private final Date start;
private final Date end;
public Period(Date start, Date end) {
// 構造時防禦性複製:防止外部修改傳入的 Date
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (this.start.compareTo(this.end) > 0)
throw new IllegalArgumentException("start after end");
}
public Date getStart() {
// 返回時防禦性複製:防止調用者修改內部 Date
return new Date(start.getTime());
}
// 同理 getEnd()
}
// 題 1
String s = "hello";
modify(s);
System.out.println(s); // ?
void modify(String s) { s = "world"; }
// 答:hello (引用傳值,s 指向新物件)
// 題 2
int[] arr = {1,2,3};
modify(arr);
System.out.println(Arrays.toString(arr)); // ?
void modify(int[] arr) { arr[0] = 99; }
// 答:[99, 2, 3] (修改陣列內容,引用同一陣列物件)
// 題 3
int[] arr = {1,2,3};
modify(arr);
System.out.println(Arrays.toString(arr)); // ?
void modify(int[] arr) { arr = new int[]{4,5,6}; }
// 答:[1, 2, 3] (重新賦值只改變局部引用)
// 題 4
List list = new ArrayList<>();
modify(list);
System.out.println(list.isEmpty()); // ?
void modify(List list) { list = new ArrayList<>(); list.add("x"); }
// 答:true (重新賦值無效)