Java 中物件賦值(B = A)僅複製引用,雙方指向同一堆記憶體。clone() 預設為淺拷貝:基本型別與 String(不可變)複製值,但引用型別(集合、自訂物件、陣列)仍共享同一實例。修改副本的引用成員會影響原物件,導致難以追蹤的 Bug。
利用 JVM 序列化機制:
ObjectOutputStream),此過程遞迴遍歷所有可達物件,產生獨立的位元組表示。ObjectInputStream),在堆上分配全新實例,遞迴還原所有欄位值。import java.io.*;
public final class DeepCloneUtil {
private DeepCloneUtil() {}
/**
* 深度克隆任意可序列化物件
* @param obj 來源物件,必須實作 Serializable
* @param 物件類型
* @return 完全獨立的深度副本
* @throws RuntimeException 序列化/反序列化失敗時拋出
*/
@SuppressWarnings("unchecked")
public static T deepClone(T obj) {
if (obj == null) return null;
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(obj);
oos.flush();
try (ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis)) {
return (T) ois.readObject();
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("Deep clone failed: " + obj.getClass(), e);
}
}
}
import java.io.Serializable;
import java.util.*;
public class Demo implements Serializable {
private String name;
private List tags;
private Address address; // 巢狀物件
// getter/setter/建構子省略
public static void main(String[] args) {
Demo original = new Demo("Original", Arrays.asList("a", "b"), new Address("Taipei"));
Demo cloned = DeepCloneUtil.deepClone(original);
// 驗證獨立性
cloned.getTags().add("c");
cloned.getAddress().setCity("Kaohsiung");
System.out.println("Original tags: " + original.getTags()); // [a, b]
System.out.println("Cloned tags: " + cloned.getTags()); // [a, b, c]
System.out.println("Original city: " + original.getAddress().getCity()); // Taipei
System.out.println("Cloned city: " + cloned.getAddress().getCity()); // Kaohsiung
}
}
class Address implements Serializable {
private String city;
// ...
}
| 條件 | 說明 |
|---|---|
| 實作 Serializable | 所有參與克隆的類別(含巢狀物件、集合元素)必須實作 java.io.Serializable |
| static/transient 欄位 | static 屬類別不屬實例,transient 會被序列化忽略,克隆後為預設值(null/0/false) |
| serialVersionUID | 強烈建議顯式宣告,避免版本不一致導致 InvalidClassException |
| 非序列化資源 | Thread、Socket、DB Connection、檔案句柄等不可序列化,克隆會失敗 |
| 效能開銷 | 序列化涉及反射、位元組流、記憶體分配,比淺拷貝慢 10-100 倍,不適合高頻熱路徑 |
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 序列化深度克隆 | 通用、零侵入、處理複雜物件圖 | 效能差、需 Serializable、無法處理非序列化資源 | 配置物件複製、快照、測試資料構建、低頻操作 |
| 手寫複製建構子/工廠 | 效能最高、完全可控、無序列化限制 | 樣板代碼多、維護成本高、易遺漏欄位 | 核心領域模型、高頻熱路徑、不可序列化物件 |
| 第三方庫(Kryo, FST, Jackson) | 效能優於 JDK 序列化、支援更多類型 | 引入依賴、需配置 | 高效能序列化/克隆、緩存、分散式傳輸 |
| 建構器模式 + 手動複製 | 語意清晰、不可變物件友善 | 冗長 | DTO、Value Object |
copy() 或 withXxx() 方法,明確語意、效能可控。DeepCloneUtil 開箱即用。ByteArrayOutputStream 佔用巨大堆記憶體,可直接管道至檔案/網路流。序列化深度克隆是 Java 開發者工具箱中的「瑞士刀」:通用、簡潔、語意明確。但它不是銀彈——理解其機制、限制與效能特徵,並在架構層面選擇正確的複製策略,才能寫出健壯、高效的系統。