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

Java 深拷貝兩大核心實現方式:Cloneable 模式 vs 序列化模式全解析

     編輯:Java 相關知識

淺拷貝僅複製引用,深拷貝遞歸複製對象圖。Java 提供兩大原生深拷貝路徑:實現 `Cloneable` 重寫 `clone()`、實現 `Serializable` 走序列化反序列化。本文對比兩種模式優劣、陷阱、適用場景,給出生產級代碼範本。

核心概念釐清

維度淺拷貝深拷貝
基本類型/不可變對象值複製值複製
可變引用類型字段**複製引用**,指向同一堆對象**遞歸複製對象**,指向新獨立對象
修改拷貝對象字段影響原對象是 (共享可變狀態)否 (完全隔離)
典型場景DTO 傳遞、不可變對象、性能敏感且確認無共享風險對象圖快照、撤銷/重做、多線程隔離副本、緩存存儲

方式一:Cloneable + clone() 模式 (手動控制、高性能)

協議契約

  • 實現 `Cloneable` 標記介面 (無方法,僅標記)
  • 重寫 `protected Object clone() throws CloneNotSupportedException` → 拋 `public`
  • 首行調用 `super.clone()` 獲取淺拷貝對象 (JVM 內存級複製,極快)
  • **手動深拷貝** 所有可變引用字段

標準實現範本


public class Professor implements Cloneable {
    private String name;
    private int age;
    private List papers; // 可變引用字段
    
    // 構造器、getter/setter 省略
    
    @Override
    public Professor clone() {
        try {
            Professor cloned = (Professor) super.clone(); // 淺拷貝:基本類型複製、引用共享
            // 深拷貝可變字段
            cloned.papers = new ArrayList<>(this.papers); // 集合深拷貝
            // 若 papers 元素也是可變對象,需遞歸拷貝元素
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError("Cloneable implemented", e); // 不可能發生
        }
    }
}

public class Student implements Cloneable {
    private String name;
    private int age;
    private Professor advisor; // 可變引用字段
    
    @Override
    public Student clone() {
        try {
            Student cloned = (Student) super.clone();
            // 深拷貝 advisor:要求 Professor 也實現 Cloneable
            cloned.advisor = this.advisor == null ? null : this.advisor.clone();
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(e);
        }
    }
}

驗證代碼


Professor p = new Professor("Wang", 50, Arrays.asList("Paper1", "Paper2"));
Student s1 = new Student("Zhang", 18, p);
Student s2 = s1.clone();

s2.getAdvisor().setName("Li");      // 修改拷貝對象的關聯對象
s2.getAdvisor().getPapers().add("Paper3");

System.out.println(s1.getAdvisor().getName()); // Wang (未受影響)
System.out.println(s1.getAdvisor().getPapers()); // [Paper1, Paper2] (未受影響)

Cloneable 模式優缺點

優點缺點
性能極高 (JVM 內存複製 + 少量手動 new)侵入性強:需修改類、實現介面、重寫方法、處理異常
精確控制:哪些字段深拷貝、哪些共享、如何拷貝集合/Map遞歸拷貝易漏字段、循環引用導致棧溢出、final 字段無法修改
無序列化開銷 (無 IO、無反射)破壞封裝:構造器未執行,final 字段可能未初始化、不變約束可能破壞
可拷貝非 Serializable 對象 (如 Thread、Socket、連接池對象)繼承體系複雜時:父類未實現 Cloneable 導致超類字段淺拷貝

進階:解決 final 字段與循環引用


// final 字段深拷貝:clone() 中無法賦值 final,需用反射或放棄 final
// 循環引用:引入「已拷貝對象映射表」
public class DeepCloneContext {
    private final Map clonedMap = new IdentityHashMap<>();
    
    public  T deepClone(T obj) {
        if (obj == null) return null;
        if (clonedMap.containsKey(obj)) return (T) clonedMap.get(obj);
        
        // ... 執行 clone 邏輯,將新對象放入 clonedMap 再遞歸拷貝字段
    }
}

方式二:序列化反序列化模式 (通用、零侵入、自動遞歸)

原理

對象圖 → 字節流 (序列化) → 字節流 → 新對象圖 (反序列化)。JVM 自動遍歷對象圖,處理循環引用、共享引用、final 字段 (反序列化可賦值 final)。

標準實現


public class DeepCloneUtils {
    // 通用深拷貝 (要求對象圖所有類實現 Serializable)
    @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); // 序列化
            try (ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
                 ObjectInputStream ois = new ObjectInputStream(bis)) {
                return (T) ois.readObject(); // 反序列化
            }
        } catch (IOException | ClassNotFoundException e) {
            throw new IllegalStateException("Deep clone failed: " + obj.getClass(), e);
        }
    }
}

// 使用
Student s2 = DeepCloneUtils.deepClone(s1); // 一行代碼搞定任意深度對象圖

序列化模式優缺點

優點缺點
零侵入:不修改業務類,僅需實現 Serializable (標記介面)性能差:IO 流開銷、反射調用、字節碼編解碼,比 clone 慢 10-100 倍
自動遞歸:完美處理任意深度對象圖、循環引用、共享引用、final 字段所有類必須 Serializable,第三方庫類無法修改時無法使用
天然支援跨 JVM 進程傳輸 (RPC、分布式緩存、會話複製)serialVersionUID 維護負擔,版本演進易報 InvalidClassException
可配合 Externalizable 自定義二進制格式提升性能transient 字段不會被拷貝 (需手動處理或放棄 transient)

性能優化:Kryo / FST / ProtoStuff (生產級替代)


// Kryo: 高性能序列化庫,非標準 Java 序列化,需註冊類
Kryo kryo = new Kryo();
kryo.register(Student.class);
kryo.register(Professor.class);
Student s2 = kryo.copy(s1); // 深拷貝,比 Java 序列化快 10 倍+

// FST: 兼容 Java 序列化接口,無需註冊,極快
FSTConfiguration conf = FSTConfiguration.createDefaultConfiguration();
Student s2 = conf.copy(s1);

現代最佳實踐:不可變對象 + 構造器複製 / Record / Lombok


// Java 16+ Record: 天然不可變,無需深拷貝,直接共享安全
public record Student(String name, int age, Professor advisor) {
    // 如需修改某字段產生新對象
    public Student withName(String newName) {
        return new Student(newName, age, advisor); // advisor 不可變可直接共享
    }
}

// Lombok @Value (不可變) + @With (生成 withXxx 方法)
@Value
@With
public class Student {
    String name;
    int age;
    Professor advisor; // 若 Professor 也不可變,整個對象圖安全共享
}

範式轉移:與其費心深拷貝可變對象圖,不如**從源頭設計為不可變對象**。不可變對象天然線程安全、可自由共享、無需拷貝、構造時驗證保證合法狀態。深拷貝僅作為「不得不與可變遺留系統交互」的戰術手段。

選型決策樹

需要深拷貝?
├─ 對象圖全是不可變/基本類型 → 直接共享,無需拷貝 ✅
├─ 能重構為不可變 (Record/@Value) → 重構,消除拷貝需求 ✅
├─ 必須拷貝可變對象圖:
│   ├─ 高性能場景、可控制字段、類可修改 → Cloneable + 手動深拷貝
│   ├─ 通用工具、第三方類、對象圖複雜/未知 → 序列化 (Kryo/FST 推薦)
│   └─ 分布式傳輸/持久化 → 序列化 (Protobuf/Avro/JSON 更佳)
└─ 單層對象/少量字段 → 手動 new + setter / MapStruct 映射

總結

`Cloneable` 給控制權與性能,`Serializable` 給通用性與便利。但**最好的深拷貝是不需要深拷貝**——擁抱不可變設計,用 Record/@Value 構建值對象,用 `withXxx` 產生新狀態,讓對象圖天然安全共享。遺留系統中,引入 Kryo/FST 替代原生序列化,兼顧性能與零侵入。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved