首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 深度克隆實戰:序列化法與替代方案完整對比

     編輯:Java 相關知識

什麼是深度克隆?

淺克隆僅複製引用,深度克隆遞歸複製對象圖中所有可達對象,確保原對象與副本完全隔離。序列化法利用 JVM 序列化機制「寫出→讀入」實現深度複製,是通用性最強的方案之一。

序列化法核心實現

public final class SerializationCloneUtil {
    private SerializationCloneUtil() {}
    
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> 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 IllegalStateException("深度克隆失敗: " + obj.getClass(), e);
        }
    }
}

完整可運行範例

public class DeepCloneDemo implements Serializable {
    private static class Address implements Serializable {
        String city; String street;
        Address(String city, String street) { this.city = city; this.street = street; }
        @Override public String toString() { return city + " " + street; }
    }
    
    private static class Person implements Serializable {
        String name; int age; Address address; List<String> tags;
        Person(String name, int age, Address addr, List<String> tags) {
            this.name = name; this.age = age; this.address = addr; this.tags = tags;
        }
        @Override public String toString() {
            return "Person{name='" + name + "', age=" + age + ", addr=" + address + ", tags=" + tags + "}";
        }
    }
    
    public static void main(String[] args) {
        Person original = new Person("Henry", 22, 
            new Address("Taipei", "Renai Rd"), 
            Arrays.asList("java", "spring"));
        
        Person cloned = SerializationCloneUtil.deepClone(original);
        
        // 驗證隔離性
        cloned.getTags().add("cloned-tag");
        cloned.getAddress().city = "Kaohsiung";
        
        System.out.println("Original: " + original);
        System.out.println("Cloned:   " + cloned);
        // 原對象 tags 不變,address.city 仍為 Taipei
    }
}

序列化法優缺點分析

優點缺點
通用性強:任意實現 Serializable 的對象圖皆可複製效能較差:涉及字節流編解碼、反射,比手寫複製慢 10-100 倍
零侵入:無需修改業務類、無需實現 Cloneable要求所有可達類實現 Serializable,否則拋 NotSerializableException
自動處理循環引用、複雜集合、代理對象transient 欄位會丟失(需自行處理)
支援跨 JVM 進程傳輸(天然支援分散式)無法複製非序列化資源(Socket、Thread、DB Connection)

主流替代方案對比

方案效能適用場景備註
手寫複製構造器/Setter最高核心領域模型、高頻調用維護成本高,易漏欄位
Apache Commons Lang SerializationUtils.clone()同序列化法快速接入、舊專案內部同樣用序列化
JSON 循環轉換中等DTO、VO、跨語言互操作需無參構造器、失去型別資訊(多態)
MapStruct / Lombok @Builder編譯期生成,極快新專案、DTO 轉換需註解處理器,不支援任意對象圖
Kryo / FST / Protobuf極高高效能序列化、緩存、RPC需註冊類、配置較複雜

選型建議

  • 低頻、通用、對象圖複雜:序列化法(或 Commons Lang 封裝)
  • 高頻、核心領域模型:手寫複製或 MapStruct
  • DTO/VO 層轉換:MapStruct / Lombok Builder / JSON
  • 分散式緩存/消息傳輸:Kryo / Protobuf / JSON

避坑提醒

  1. 確保所有可達類實現 Serializable,建議加上 serialVersionUID。
  2. 標記不需複製的欄位為 transient(如 Logger、緩存)。
  3. 大對象圖克隆可能導致 OOM,考慮分批處理或改用流式複製。
  4. 單元測試驗證:修改副本不影響原對象、循環引用不棧溢出。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved