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

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

     編輯:Java 相關知識

核心概念對比

維度淺拷貝深拷貝
基本型別欄位值複製值複製
引用型別欄位複製引用地址 (共用同一物件)遞歸複製引用物件 (全新物件圖)
修改影響修改引用物件會影響原物件完全隔離,互不影響
典型實現Object.clone() 默認行為重寫 clone() 遞歸複製 / 序列化反序列化

clone() 機制與協議

1. 協約要求

  • x.clone() != x (新物件)
  • x.clone().getClass() == x.getClass() (同類型)
  • x.clone().equals(x) (若正確實現 equals)

2. 標準實現模板

public class Person implements Cloneable {
    private String name;
    private int age;
    private Address address; // 引用型別

    @Override
    public Person clone() {
        try {
            Person cloned = (Person) super.clone(); // 淺拷貝
            // 深拷貝引用欄位
            cloned.address = this.address.clone(); // Address 需實現 Cloneable
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError("不可發生", e);
        }
    }
}

代碼演示:淺拷貝陷阱與深拷貝修正

場景:Student 引用 Professor


// Professor 實現 Cloneable
class Professor implements Cloneable {
    String name; int age;
    // getter/setter/clone()...
}

// Student 淺拷貝 (危險)
class Student implements Cloneable {
    String name; int age; Professor prof;
    @Override public Student clone() {
        try { return (Student) super.clone(); }
        catch (CloneNotSupportedException e) { throw new AssertionError(e); }
    }
}

// 測試
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); // 輸出「李教授」—— s1 被污染!

深拷貝修正


class Student implements Cloneable {
    // ...
    @Override public Student clone() {
        try {
            Student cloned = (Student) super.clone();
            cloned.prof = this.prof.clone(); // 關鍵:深拷貝引用物件
            return cloned;
        } catch (CloneNotSupportedException e) { throw new AssertionError(e); }
    }
}

再次測試,s1.prof.name 保持「王教授」,隔離成功。

序列化實現深拷貝 (通用、無需實現 Cloneable)


public  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)) {
            @SuppressWarnings("unchecked")
            T cloned = (T) ois.readObject();
            return cloned;
        }
    } catch (IOException | ClassNotFoundException e) {
        throw new RuntimeException("深拷貝失敗", e);
    }
}

前提與限制

  • 物件圖中所有類必須實現 Serializable。
  • 非序列化欄位標記 transient 會被跳過 (需手動處理)。
  • 效能較差 (IO、反射開銷),不適合高頻調用。
  • 單例、線程池、連線等不可序列化物件會破壞語義。

現代替代方案

方案優點缺點
複製建構子 / 工廠方法可讀、可控、無反射、支援不可變物件需手動編寫、維護成本
MapStruct / Lombok @Builder編譯期生成、高性能、類型安全引入依賴、學習成本
Jackson / Gson 序列化轉換通用、支援複雜圖、可配置需無參建構子、效能中等
Immutable Collections / Records (Java 16+)天然不可變、無需拷貝適用於新代碼、數據類

最佳實踐建議

  1. 優先使用不可變物件 (final 欄位、無 setter、Record),從根源消除拷貝需求。
  2. 必須可變時:提供明確的 copy() / deepCopy() 方法,內部用複製建構子或 MapStruct 實現,**避免使用 clone()**。
  3. 集合拷貝:new ArrayList<>(list) (淺) / 串流 list.stream().map(Item::deepCopy).collect(toList()) (深)。
  4. 文檔化拷貝語義:在 Javadoc 明確聲明是淺/深,防止調用者誤用。

總結

淺拷貝共享引用,深拷貝遞歸複製。clone() 設計缺陷多 (需實現 Cloneable、拋受檢異常、默認淺拷貝),**現代 Java 開發強烈建議棄用 clone()**,改用複製建構子、工廠方法、MapStruct 或不可變設計。序列化深拷貝僅作為 legacy 系統兼容或通用工具方法的最後手段。

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