對象拷貝是業務開發中高頻操作(DTO 轉 VO、快照保存、緩存隔離等)。本文系統對比 clone()、反射拷貝、序列化深拷貝、MapStruct、BeanUtils、Hutool 等主流方案的原理、效能、深/淺拷貝支援度與適用場景,助你選型最優方案。
| 術語 | 定義 | 典型場景 |
|---|---|---|
| 淺拷貝 | 僅拷貝引用型別字段的引用值,引用同一物件 | 不可變物件、字符串、基本型別包裝類 |
| 深拷貝 | 遞歸拷貝所有引用型別字段指向的物件,完全隔離 | 可變集合、嵌套物件、業務快照 |
Cloneable 標記介面(否則拋 CloneNotSupportedException)。clone() 為 public,內部調用 super.clone()。super.clone() 為 native 方法,執行淺拷貝(逐位複製)。this.list = (List) list.clone();。final 字段(拷貝後無法重新賦值)。BeanUtils.copyProperties(dest, src); // 僅淺拷貝,忽略 null/類型不匹配
BeanUtils.copyProperties(src, dest, "id", "createTime"); // 支援忽略字段
BeanUtil.copyProperties(src, dest); // 支援 Map↔Bean、字段別名、日期格式化
BeanUtil.toBean(src, Dest.class); // 新建目標對象
CopyOptions 控制編輯策略。public static <T> T deepCopy(T source, Class<T> clazz) {
if (source == null) return null;
try {
T target = clazz.getDeclaredConstructor().newInstance();
for (Field f : clazz.getDeclaredFields()) {
f.setAccessible(true);
Object val = f.get(source);
if (val == null) continue;
// 基本型別、String、不可變類直接賦值
if (isImmutable(f.getType())) {
f.set(target, val);
} else if (val instanceof Collection) {
Collection<?> srcCol = (Collection<?>) val;
Collection<Object> newCol = srcCol.getClass().getDeclaredConstructor().newInstance();
for (Object item : srcCol) newCol.add(deepCopy(item, item.getClass()));
f.set(target, newCol);
} else if (val instanceof Map) {
// Map 深拷貝邏輯類似
} else {
// 遞歸拷貝嵌套物件
f.set(target, deepCopy(val, val.getClass()));
}
}
return target;
} catch (Exception e) {
throw new RuntimeException("深拷貝失敗", e);
}
}
缺點:效能低、循環引用棧溢出、泛型擦除導致集合元素類型丟失、單例/代理物件拷貝語義錯誤。
public static <T> T serializationCopy(T obj) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(obj);
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
return (T) ois.readObject();
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("序列化拷貝失敗", e);
}
}
Serializable,transient 字段會丟失。@Mapper(componentModel = "spring")
public interface UserMapper {
UserDTO toDTO(UserEntity entity);
@Mapping(target = "addressList", source = "addresses")
UserVO toVO(UserDTO dto);
// 集合映射
List toDTOList(List entities);
}
set/get 代碼,效能等同手寫,無反射開銷。@Mapping + 轉換器手動 new 集合元素。| 方案 | 耗時 (ms) | 相對基準 | 深拷貝 | 推薦指數 |
|---|---|---|---|---|
| 手寫 set/get | 12 | 1.0x | 可控 | ⭐⭐⭐⭐⭐ (簡單物件) |
| MapStruct | 14 | 1.2x | 需配置 | ⭐⭐⭐⭐⭐ (生產首選) |
| Spring BeanUtils | 180 | 15x | 否 | ⭐⭐⭐ (DTO互轉) |
| Hutool BeanUtil | 210 | 17x | 否 | ⭐⭐⭐ (功能豐富) |
| 反射遞歸深拷貝 | 3,500 | 290x | 是 | ⭐ (測試/臨時) |
| 序列化 | 12,000 | 1000x | 是 | ⭐ (持久化/跨進程) |
objectMapper.convertValue(obj, clazz)。clone() 設計缺陷多,現代開發不推薦使用。