對象拷貝是 Java 開發高頻操作,但 `clone()` 默認僅**淺拷貝**,導致引用類型字段共享底層對象,修改副本影響原對象。本文徹底解析淺/深拷貝區別、五種深拷貝實現方案、性能對比、適用場景選型指南。
| 維度 | 淺拷貝 | 深拷貝 |
|---|---|---|
| 基本類型/不可變對象 | 值複製 | 值複製 |
| 引用類型字段 | **複製引用** (指向同一對象) | **遞歸複製對象圖** (新建獨立對象) |
| 修改副本影響原對象 | 是 (引用字段) | 否 (完全隔離) |
| 典型實現 | `Object.clone()`、構造器複製引用 | 序列化、手動遞歸、第三方庫 |
class Address implements Serializable {
String city;
// getter/setter/toString
}
class User implements Cloneable, Serializable {
String name;
Address address; // 引用類型
@Override
protected User clone() throws CloneNotSupportedException {
return (User) super.clone(); // 默認淺拷貝
}
}
public class ShallowCopyTrap {
public static void main(String[] args) throws Exception {
User u1 = new User();
u1.name = "Alice";
u1.address = new Address();
u1.address.city = "Beijing";
User u2 = u1.clone(); // 淺拷貝
u2.name = "Bob";
u2.address.city = "Shanghai"; // 修改副本地址
System.out.println(u1.address.city); // Shanghai !!! 原對象被污染
}
}
public static <T extends Serializable> 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)) {
return (T) ois.readObject();
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("深拷貝失敗", e);
}
}
class DeepUser implements Cloneable {
String name;
Address address;
@Override
protected DeepUser clone() {
try {
DeepUser copy = (DeepUser) super.clone();
copy.address = this.address.clone(); // 手動深拷貝引用字段
return copy;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
}
// Address 也需實現 Cloneable 並重寫 clone()
// Jackson
ObjectMapper mapper = new ObjectMapper();
User copy = mapper.readValue(mapper.writeValueAsString(user), User.class);
// Gson
Gson gson = new Gson();
User copy = gson.fromJson(gson.toJson(user), User.class);
User copy = SerializationUtils.clone(user); // 內部用序列化
// 或
User copy = SerializationUtils.roundtrip(user); // 同義
適用於 **DTO ↔ Entity** 互轉,非通用深拷貝,但生產環境最常用:
@Mapper
interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
UserDto toDto(User user); // 自動生成深拷貝代碼
}
| 方案 | 耗時 (μs/op) | 相對性能 | 備註 |
|---|---|---|---|
| 手動 clone() | 0.15 | 1x (基準) | 最快,零開銷 |
| Kryo (序列化庫) | 0.45 | 3x | 高性能二進制序列化 |
| JSON (Jackson) | 1.2 | 8x | 易讀,適合配置/網絡 |
| Java 序列化 | 3.5 | 23x | 通用但慢 |
需要深拷貝?
├─ 是否高頻/熱路徑/極致性能敏感?
│ ├─ 是 → 手動 clone() / Kryo / Protobuf
│ └─ 否 → 對象是否可序列化?
│ ├─ 是 → 簡單場景:SerializationUtils.clone()
│ │ 複雜對象圖/循環引用:Java 序列化 / Kryo
│ └─ 否 (含第三方不可變類/無無參構造器)
│ ├─ DTO/Entity 轉換 → MapStruct (編譯期生成)
│ ├─ 通用拷貝 → JSON (Jackson/Gson) / BeanUtils.copyProperties
│ └─ 測試/臨時 → JSON