Aliases a = new Aliases();
Aliases b = a; // 發生了什麼?
b.i++;
System.out.println(a.i); // 輸出 2 —— a 也變了!
這不是 Bug,這是 Java引用語義的核心體現。本文深入剖析別名現象、方法參數傳遞機制、clone() 深淺拷貝陷阱、序列化拷貝對比,並給出「不可變對象」與「防禦性拷貝」的現代最佳實踐。
Aliases a = new Aliases(1);
Aliases b = a; // b 與 a 指向堆中同一對象
b.i = 2;
assert a.i == 2; // 同一對象,當然相等
public void increment(Aliases obj) {
obj.i++; // 修改的是調用者傳入的對象
}
Aliases a = new Aliases(1);
increment(a);
assert a.i == 2; // 原對象被修改
鐵律:Java 只有值傳遞。傳遞對象時,傳遞的是引用的副本。副本指向同一堆對象,因此方法內可修改對象內容,但無法讓參數指向新對象(如 obj = new Aliases() 不影響外部)。
public interface Cloneable { } // 空接口,無 clone() 方法!
clone() 定義在 Object 中為 protected native Object clone() throws CloneNotSupportedException。實現 Cloneable 只是告訴 JVM「允許調用 super.clone()」,否則拋出異常。
class Shallow implements Cloneable {
int primitive = 42;
String immutable = "hello";
List mutable = new ArrayList<>(Arrays.asList("a", "b"));
@Override
protected Shallow clone() throws CloneNotSupportedException {
return (Shallow) super.clone(); // 默認淺拷貝
}
}
Shallow s1 = new Shallow();
Shallow s2 = s1.clone();
// s1.primitive != s2.primitive (基本類型獨立)
// s1.immutable == s2.immutable (不可變對象共享安全)
// s1.mutable == s2.mutable (可變對象共享危險!)
s2.mutable.add("c");
assert s1.mutable.contains("c"); // s1 也被修改了
class Deep implements Cloneable {
int primitive = 42;
Date mutableDate = new Date();
List mutableList = new ArrayList<>();
@Override
protected Deep clone() {
try {
Deep cloned = (Deep) super.clone();
// 手動深拷貝可變字段
cloned.mutableDate = (Date) mutableDate.clone();
cloned.mutableList = new ArrayList<>(mutableList);
// 若集合元素可變,需遞歸深拷貝
return cloned;
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // 不可能發生
}
}
}
Cloneable 接口clone() 為 publicsuper.clone() 獲取淺拷貝super.clone() 保證)CloneNotSupportedException 或包裝為非受檢異常List/Map 接口無 clone(),需具體實現類(如 ArrayList.clone())Serializable + Cloneable 易產生不一致狀態| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 複製構造函數 | 類型安全、可讀、支持接口類型、可轉換實現 | 需手動編寫、繼承需協調 | 領域模型、DTO、值對象 |
| 靜態工廠方法 | 命名清晰、可返回子類型、可緩存 | 同複製構造函數 | 單例、享元、複雜構建 |
| 序列化/反序列化 | 自動深拷貝、處理複雜對象圖 | 性能極差、需實現 Serializable、 transient 處理 | 深拷貝複雜對象圖、跨進程傳輸 |
| 第三方庫 | 一行代碼、處理循環引用、性能優化 | 引入依賴、黑盒風險 | 通用工具類、快速原型 |
| 不可變對象 | 根本消除拷貝需求、線程安全、可自由共享 | 修改需創建新對象、需語言/框架支持 | 值對象、配置、事件、DTO |
public final class UserProfile { // 不可變類
private final String username;
private final LocalDate birthday;
private final List tags; // 內部防禦性拷貝
public UserProfile(String username, LocalDate birthday, List tags) {
this.username = username;
this.birthday = birthday;
this.tags = tags == null ? List.of() : List.copyOf(tags); // Java 10+ 不可變副本
}
// 複製構造函數:支持選擇性修改
public UserProfile withUsername(String newUsername) {
return new UserProfile(newUsername, this.birthday, this.tags);
}
public UserProfile withTags(List newTags) {
return new UserProfile(this.username, this.birthday, newTags);
}
// Getter 返回不可變視圖
public List getTags() { return tags; } // 已是不可變列表
}
public class Period {
private final LocalDate start;
private final LocalDate end;
public Period(LocalDate start, LocalDate end) {
// 構造函數防禦性拷貝(雖然 LocalDate 不可變,但示例原則)
this.start = LocalDate.from(start);
this.end = LocalDate.from(end);
if (this.start.isAfter(this.end)) throw new IllegalArgumentException();
}
// Getter 返回不可變對象直接安全
public LocalDate getStart() { return start; }
public LocalDate getEnd() { return end; }
// 若內部持有可變對象(如 Date、集合、數組)
private final Date createdAt = new Date();
public Date getCreatedAt() {
return (Date) createdAt.clone(); // 防禦性拷貝:返回副本
}
private final String[] tags = {"java", "clone"};
public String[] getTags() {
return tags.clone(); // 數組 clone() 返回淺拷貝,元素為 String 安全
}
}
final 類、final 字段、無 setter、集合用 List.copyOf/Set.copyOf/Map.copyOf(Java 10+)或 Guava ImmutableXxxCloneablenew ArrayList<>(src)、new HashMap<>(src)、List.copyOf(src)(不可變視圖)SerializationUtils.clone()、Dozer/MapStruct(對象映射)、Kryo(高性能序列化)理解別名與引用語義,是寫出正確 Java 代碼的前提。clone() 作為早期 API 留下的歷史包袱,在現代開發中已非首選。擁抱不可變性、使用複製構造函數、實踐防禦性拷貝,才是構建健壯、可維護、線程安全 Java 系統的正道。