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

Java 深拷貝與淺拷貝完全指南:clone()、序列化、最佳實踐

     編輯:Java 相關知識

核心概念區分

維度淺拷貝深拷貝
基本類型值複製值複製
引用類型**複製引用地址** (共用同一對象)**遞歸複製對象** (新建獨立對象)
修改影響修改拷貝對象的引用屬性,原對象也變互不影響,完全隔離
實現方式Object.clone() 默認行為重寫 clone() / 序列化 / 三方庫

Cloneable 接口與 clone() 規約

  • 實現 Cloneable (標記接口),否則拋 CloneNotSupportedException。
  • 重寫 clone() 為 public,調用 super.clone() 獲取淺拷貝對象。
  • 規約:x.clone() != x, x.clone().getClass() == x.getClass(), x.clone().equals(x) (若覆寫 equals)。

代碼演示:淺拷貝陷阱

class Professor { String name; int age; }
class Student implements Cloneable {
    String name; int age; Professor prof;
    public Student(String n, int a, Professor p) { name=n; age=a; prof=p; }
    @Override protected Student clone() {
        try { return (Student) super.clone(); } // 淺拷貝!
        catch (CloneNotSupportedException e) { throw new AssertionError(); }
    }
}

Professor p = new Professor("王五", 50);
Student s1 = new Student("張三", 18, p);
Student s2 = s1.clone();
s2.prof.name = "李四"; // 修改 s2 的教授
System.out.println(s1.prof.name); // 輸出「李四」!原對象被污染

深拷貝方案一:重寫 clone() 遞歸複製

class Professor implements Cloneable {
    String name; int age;
    @Override protected Professor clone() {
        try { return (Professor) super.clone(); }
        catch (CloneNotSupportedException e) { throw new AssertionError(); }
    }
}
class Student implements Cloneable {
    String name; int age; Professor prof;
    @Override protected Student clone() {
        Student s = (Student) super.clone(); // 淺拷貝基本類型
        s.prof = prof.clone(); // 關鍵:深拷貝引用對象
        return s;
    }
}
// 現在 s2.prof.name = "李四" 不會影響 s1

缺點:對象圖複雜時需層層實現 Cloneable,維護成本高,循環引用會棧溢出。

深拷貝方案二:序列化反序列化 (通用、簡單)

import java.io.*;

public class DeepCloneUtil {
    @SuppressWarnings("unchecked")
    public static  T deepClone(T obj) {
        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 RuntimeException("深拷貝失敗", e);
        }
    }
}

// 使用:要求所有類實現 Serializable
Student s2 = DeepCloneUtil.deepClone(s1);

優點:一行代碼處理任意複雜對象圖,自動處理循環引用。
缺點:性能較差 (IO、反射)、要求 Serializable、transient 字段不會被拷貝。

方案三:第三方庫 (生產環境推薦)

  • Apache Commons Lang:SerializationUtils.clone(obj) (序列化)、CloneUtils.clone(obj) (反射複製)。
  • Dozer / MapStruct:映射式複製,適合 DTO 轉換。
  • Jackson/Gson:JSON 序列化反序列化複製,跨語言兼容。

性能對比 (百萬次循環)

// 簡單對象: clone() ~ 50ms, 序列化 ~ 500ms, JSON ~ 800ms
// 複雜對象圖: clone() 需手寫遞歸, 序列化/JSON 更優

最佳實踐建議

  1. **優先不可變對象** (final 字段、無 setter),根源消除拷貝需求。
  2. **構造器/工廠方法複製**:new Student(s1.getName(), s1.getAge(), s1.getProf()),最清晰、性能最好。
  3. **簡單對象圖用 clone()**,複雜圖用序列化或三方庫。
  4. **慎用 transient**:標記非序列化字段時,深拷貝會丟失該字段 (為 null/默認值)。
  5. **文檔說明拷貝深度**:API 註明 @return 深拷貝副本 或 淺拷貝。

深拷貝不是銀彈,**設計時減少可變共享狀態** 才是正道。

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