深度克隆是指複製對象及其引用鏈路上所有可達對象,使新舊對象圖在記憶體中完全隔離。本文通過對比淺複製與深複製的記憶體模型,詳細闡述基於 Java 序列化的深度克隆實作,並分析其適用場景與潛在風險。
CloneTest ct1 = new CloneTest();
CloneTest ct2 = ct1; // 兩個引用指向同一堆對象
ct2.myLong = 2L; // ct1.myLong 也變為 2
棧區:兩個引用變數 | 堆區:單一 CloneTest 實例
ct1.o ──────┐
├──► 同一個 Object 實例
ct2.o ──────┘
基本類型獨立,引用類型共享。修改 ct2.o 內部狀態會影響 ct1.o。
ct1.o ──────► Object@1234
ct2.o ──────► Object@5678 (完全獨立的新實例)
序列化將整個對象圖扁平化為位元組流,反序列化時在堆中重建所有對象,實現真正隔離。
import java.io.*;
public class DeepCloneTest {
// 必須實作 Serializable,建議顯式宣告 serialVersionUID
private static class CloneTest implements Serializable {
private static final long serialVersionUID = 1L;
private Object o = new Object();
public CloneTest deepClone() {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais)) {
oos.writeObject(this); // 序列化到位元組陣列
return (CloneTest) ois.readObject(); // 反序列化產生新對象圖
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("深度克隆失敗", e);
}
}
}
public static void main(String[] args) {
CloneTest ct1 = new CloneTest();
CloneTest ct2 = ct1.deepClone();
System.out.println("ct1: " + ct1); // 不同引用
System.out.println("ct2: " + ct2);
System.out.println("ct1.o: " + ct1.o); // 不同對象
System.out.println("ct2.o: " + ct2.o);
// 輸出證明 ct1.o != ct2.o
}
}
class A implements Serializable { B b; } // B 也必須 implements Serializable
否則拋出 NotSerializableException。static 屬於類,不屬於實例transient 明確標記不序列化(如資料庫連線、執行緒鎖)null 或預設值。InvalidClassException)。readResolve() 保護單例:
private Object readResolve() { return INSTANCE; }clone() 慢 100-1000 倍,**不適合高頻調用**。| 方案 | 深度 | 效能 | 適用條件 | 備註 |
|---|---|---|---|---|
| clone() 手動深複製 | 可控 | ★★★★★ | 可修改源碼、層級淺 | 最繁瑣但最快 |
| 序列化 | 完整 | ★☆☆☆☆ | 全鏈路 Serializable | 通用性最強 |
| JSON 序列化 (Jackson/Gson) | 完整 | ★★☆☆☆ | 無循環引用、字段可見 | 跨語言、可讀性好 |
| Kryo / FST | 完整 | ★★★★☆ | 需引入第三方庫 | 高效能二進位序列化 |
| MapStruct | 可控 | ★★★★★ | 編譯期生成 | 現代首選,需定義映射 |
clone() 或 MapStruct序列化深度克隆是「萬能膠水」,能處理任意複雜對象圖,但付出的是巨大的效能代價。理解其底層機制(位元組流扁平化與重建)、序列化協議限制(static/transient/serialVersionUID),並在架構層面控制使用頻率,才能安全地在專案中應用此技術。