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

Java 對象複製深度解析:clone()、反射複製與最佳實踐

     編輯:Java 程式設計入門

對象複製是 Java 開發中高頻需求,無論是 DTO 轉換、快照保存還是緩存隔離,選對複製策略關係到效能與正確性。本文深度剖析 clone() 淺/深複製機制、反射通用複製局限,並給出生產級選型建議。

方案一:原生 clone() 機制

淺複製(預設行為)

public class ShallowClone implements Cloneable {
    private String name;
    private Address address; // 引用類型

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // 僅複製基本類型與引用地址
    }
}
// 結果:clone 物件與原物件共用同一個 Address 實例

深複製(需手動遞歸)

public class DeepClone implements Cloneable {
    private String name;
    private Address address;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        DeepClone cloned = (DeepClone) super.clone();
        // 手動深複製引用類型字段
        cloned.address = (Address) address.clone(); // Address 也需實作 Cloneable
        return cloned;
    }
}

clone() 優缺點

優點缺點
native 實作,效能極高需實作 Cloneable、處理 Checked Exception
語義清晰,JDK 原生支援深複製需手動遞歸,易漏字段、難維護
支援陣列直接複製無法複製 final 字段、私有構造函數類

方案二:反射通用複製(原文提供代碼分析)

private <T> T getBean(T targetBean, T sourceBean) {
    if (targetBean == null) return null;
    Field[] tFields = targetBean.getClass().getDeclaredFields();
    Field[] sFields = sourceBean.getClass().getDeclaredFields();
    try {
        for (Field field : tFields) {
            String fieldName = field.getName();
            if (fieldName.equals("serialVersionUID")) continue;
            // 原代碼直接跳過集合類型,無法複製
            if (field.getType() == Map.class) continue;
            if (field.getType() == Set.class) continue;
            if (field.getType() == List.class) continue;

            for (Field sField : sFields) {
                if (!sField.getName().equals(fieldName)) continue;
                Class type = field.getType();
                String setName = "set" + fieldName.substring(0,1).toUpperCase() + fieldName.substring(1);
                String getName = "get" + fieldName.substring(0,1).toUpperCase() + fieldName.substring(1);
                Method tMethod = targetBean.getClass().getMethod(setName, type);
                Method sMethod = sourceBean.getClass().getMethod(getName);
                Object value = sMethod.invoke(sourceBean);
                tMethod.invoke(targetBean, value);
            }
        }
    } catch (Exception e) {
        throw new RuntimeException("屬性複製失敗", e);
    }
    return targetBean;
}

反射複製的嚴重局限

  1. 完全不支援集合/Map 深複製:代碼直接 continue 跳過
  2. 無法處理巢狀對象:引用類型字段僅複製引用,非深複製
  3. 效能低下:反射調用比直接存取慢 10-100 倍
  4. 破壞封裝:需公開 setter/getter,無法複製不可變對象
  5. 泛型擦除問題:運行時無法獲取集合泛型類型

方案三:序列化深複製(適用可序列化對象)

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);
    }
}

適用條件:對象及所有引用鏈路實作 Serializable。效能:比 clone() 慢,但支援任意深度物件圖。

方案四:現代化工具庫(生產環境首選)

MapStruct(編譯期生成,零反射)


@Mapper
public interface UserMapper {
    UserDTO toDTO(UserEntity entity);
    List<UserDTO> toDTOs(List<UserEntity> entities);
}
  • 編譯期生成實作代碼,效能等同手寫
  • 類型安全,IDE 重構友善
  • 支援自定義映射、集合映射、巢狀對象

BeanUtils / BeanCopier(Spring / cglib)


// Spring BeanUtils - 淺複製
BeanUtils.copyProperties(source, target);

// cglib BeanCopier - 高效淺複製
BeanCopier copier = BeanCopier.create(Source.class, Target.class, false);
copier.copy(source, target, null);

Jackson / Gson(JSON 序列化中轉)


// 適用於跨層級 DTO 轉換,自動處理集合、巢狀對象
UserDTO dto = objectMapper.convertValue(entity, UserDTO.class);
// 或
UserDTO dto = gson.fromJson(gson.toJson(entity), UserDTO.class);

選型決策樹

  1. 高效能、單層級、可控類別 → clone() 或 MapStruct
  2. 跨層級 DTO 轉換、字段名不一致 → MapStruct
  3. 第三方類別、無法修改源碼 → Jackson/Gson 序列化中轉
  4. 深複製任意物件圖、可接受效能代價 → 序列化深複製
  5. 快速原型、一次性腳本 → Spring BeanUtils

總結

原文的反射複製代碼雖具通用性,但因不支援集合、深複製與效能問題,**不建議直接用於生產環境**。現代 Java 開發應優先選擇 **MapStruct**(編譯期安全、高效能)或 **Jackson**(通用性強),僅在特定受限場景下使用 clone() 或序列化深複製。理解各方案的適用邊界,才能寫出健壯可維護的複製邏輯。

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