對象複製是 Java 開發中高頻需求,無論是 DTO 轉換、快照保存還是緩存隔離,選對複製策略關係到效能與正確性。本文深度剖析 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;
}
}
| 優點 | 缺點 |
|---|---|
| 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;
}
continue 跳過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() 慢,但支援任意深度物件圖。
@Mapper
public interface UserMapper {
UserDTO toDTO(UserEntity entity);
List<UserDTO> toDTOs(List<UserEntity> entities);
}
// Spring BeanUtils - 淺複製
BeanUtils.copyProperties(source, target);
// cglib BeanCopier - 高效淺複製
BeanCopier copier = BeanCopier.create(Source.class, Target.class, false);
copier.copy(source, target, null);
// 適用於跨層級 DTO 轉換,自動處理集合、巢狀對象
UserDTO dto = objectMapper.convertValue(entity, UserDTO.class);
// 或
UserDTO dto = gson.fromJson(gson.toJson(entity), UserDTO.class);
clone() 或 MapStruct原文的反射複製代碼雖具通用性,但因不支援集合、深複製與效能問題,**不建議直接用於生產環境**。現代 Java 開發應優先選擇 **MapStruct**(編譯期安全、高效能)或 **Jackson**(通用性強),僅在特定受限場景下使用 clone() 或序列化深複製。理解各方案的適用邊界,才能寫出健壯可維護的複製邏輯。