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

Java 對象克隆深度解析:淺克隆 vs 深克隆、Cloneable 與序列化兩大實現路徑

     編輯:Java 綜合教學

Java 對象克隆深度解析:從淺克隆到深克隆的完整指南

一、為什麼需要克隆?

Object obj2 = obj1; 僅複製引用,雙方指向同一堆內存對象,修改一方影響另一方。克隆目的是**創建獨立副本**,保留當前對象狀態(字段值),互不干擾。new 創建對象屬性為初始值,無法保留運行時修改後的狀態。

二、Cloneable 接口與 Object.clone() 機制

  • Cloneable:標記接口(無方法),聲明「我支持克隆」。
  • protected native Object clone():Native 方法,執行**逐位複製(Shallow Copy)**:基本類型複製值,引用類型複製引用地址。
  • 未實現 Cloneable 調用 clone() 拋 CloneNotSupportedException。

三、淺克隆實現

public class Student implements Cloneable {
    private int number;
    private String name; // String 不可變,淺克隆安全

    @Override
    public Student clone() {
        try {
            return (Student) super.clone(); // 逐位複製
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(); // 不可能發生
        }
    }
    // getter/setter
}

測試:stu2 = stu1.clone(); stu2.setNumber(999); → stu1.number 不變,**基本類型、不可變對象(String、包裝類)淺克隆即安全**。

四、深克隆必要性:引用類型字段

class Address { String city; }
class Student implements Cloneable {
    private Address address; // 可變引用類型
    // ...
}

淺克隆下 stu1.address == stu2.address,修改 stu2.address.city 會影響 stu1。**必須深克隆**。

五、深克隆兩大實現路徑

路徑 1:遞歸調用 clone()(手動深拷貝)

class Address implements Cloneable {
    private String city;
    @Override public Address clone() {
        try { return (Address) super.clone(); }
        catch (CloneNotSupportedException e) { throw new AssertionError(); }
    }
}

class Student implements Cloneable {
    private Address address;
    @Override public Student clone() {
        Student stu = (Student) super.clone(); // 淺複製基本字段
        stu.address = this.address.clone();    // 手動深複製引用字段
        return stu;
    }
}

缺點:引用層級深、字段多時代碼繁瑣,易漏字段。

路徑 2:序列化反序列化(通用深拷貝)⭐ 推薦

import java.io.*;

public class DeepCloneUtil {
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T deepClone(T obj) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(obj); // 序列化到字節數組(副本)
            try (ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                 ObjectInputStream ois = new ObjectInputStream(bais)) {
                return (T) ois.readObject(); // 反序列化生成新對象圖
            }
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("深克隆失敗", e);
        }
    }
}

使用:Student stu2 = DeepCloneUtil.deepClone(stu1);

優點:

  • 一行代碼處理任意深度對象圖。
  • 編譯期泛型檢查 Serializable,**比運行期拋 CloneNotSupportedException 更早發現問題**。
  • 自動處理集合、Map、數組等容器深拷貝。

條件:對象及所有可達對象必須實現 Serializable,建議顯式聲明 serialVersionUID。

六、JDK 內置深克隆示例:java.util.Date

public Object clone() {
    Date d = (Date) super.clone();
    if (cdate != null) d.cdate = (BaseCalendar.Date) cdate.clone(); // 手動深複製內部 mutable 字段
    return d;
}

七、總結與選型建議

八、最佳實踐清單

  1. 優先考慮**不可變對象設計**,從源頭消除克隆需求。
  2. 若必須克隆,**首選序列化深克隆工具類**,統一異常處理、日誌記錄。
  3. 實現 Cloneable 時,**覆蓋 clone() 拋開 protected 改為 public**,返回具體類型(協變返回類型)。
  4. 為所有可序列化類顯式聲明 private static final long serialVersionUID。
  5. 單元測試驗證:clone != original、clone.getClass() == original.getClass()、修改 clone 不影響 original(含嵌套引用)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
場景推薦方案
僅基本類型 + String/不可變對象實現 Cloneable + super.clone()(淺克隆即可)
少量可變引用字段,層級固定手動遞歸 clone(),控制精確
復雜對象圖、多層嵌套、集合容器序列化深克隆(通用、可維護)
高性能熱路徑(百萬級/秒)手寫拷貝構造函數 / MapStruct / 自定義字節碼生成