淺拷貝僅複製引用,深拷貝遞歸複製對象圖。Java 提供兩大原生深拷貝路徑:實現 `Cloneable` 重寫 `clone()`、實現 `Serializable` 走序列化反序列化。本文對比兩種模式優劣、陷阱、適用場景,給出生產級代碼範本。
| 維度 | 淺拷貝 | 深拷貝 |
|---|---|---|
| 基本類型/不可變對象 | 值複製 | 值複製 |
| 可變引用類型字段 | **複製引用**,指向同一堆對象 | **遞歸複製對象**,指向新獨立對象 |
| 修改拷貝對象字段影響原對象 | 是 (共享可變狀態) | 否 (完全隔離) |
| 典型場景 | DTO 傳遞、不可變對象、性能敏感且確認無共享風險 | 對象圖快照、撤銷/重做、多線程隔離副本、緩存存儲 |
public class Professor implements Cloneable {
private String name;
private int age;
private List papers; // 可變引用字段
// 構造器、getter/setter 省略
@Override
public Professor clone() {
try {
Professor cloned = (Professor) super.clone(); // 淺拷貝:基本類型複製、引用共享
// 深拷貝可變字段
cloned.papers = new ArrayList<>(this.papers); // 集合深拷貝
// 若 papers 元素也是可變對象,需遞歸拷貝元素
return cloned;
} catch (CloneNotSupportedException e) {
throw new AssertionError("Cloneable implemented", e); // 不可能發生
}
}
}
public class Student implements Cloneable {
private String name;
private int age;
private Professor advisor; // 可變引用字段
@Override
public Student clone() {
try {
Student cloned = (Student) super.clone();
// 深拷貝 advisor:要求 Professor 也實現 Cloneable
cloned.advisor = this.advisor == null ? null : this.advisor.clone();
return cloned;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
}
Professor p = new Professor("Wang", 50, Arrays.asList("Paper1", "Paper2"));
Student s1 = new Student("Zhang", 18, p);
Student s2 = s1.clone();
s2.getAdvisor().setName("Li"); // 修改拷貝對象的關聯對象
s2.getAdvisor().getPapers().add("Paper3");
System.out.println(s1.getAdvisor().getName()); // Wang (未受影響)
System.out.println(s1.getAdvisor().getPapers()); // [Paper1, Paper2] (未受影響)
| 優點 | 缺點 |
|---|---|
| 性能極高 (JVM 內存複製 + 少量手動 new) | 侵入性強:需修改類、實現介面、重寫方法、處理異常 |
| 精確控制:哪些字段深拷貝、哪些共享、如何拷貝集合/Map | 遞歸拷貝易漏字段、循環引用導致棧溢出、final 字段無法修改 |
| 無序列化開銷 (無 IO、無反射) | 破壞封裝:構造器未執行,final 字段可能未初始化、不變約束可能破壞 |
| 可拷貝非 Serializable 對象 (如 Thread、Socket、連接池對象) | 繼承體系複雜時:父類未實現 Cloneable 導致超類字段淺拷貝 |
// final 字段深拷貝:clone() 中無法賦值 final,需用反射或放棄 final
// 循環引用:引入「已拷貝對象映射表」
public class DeepCloneContext {
private final Map
對象圖 → 字節流 (序列化) → 字節流 → 新對象圖 (反序列化)。JVM 自動遍歷對象圖,處理循環引用、共享引用、final 字段 (反序列化可賦值 final)。
public class DeepCloneUtils {
// 通用深拷貝 (要求對象圖所有類實現 Serializable)
@SuppressWarnings("unchecked")
public static T deepClone(T obj) {
if (obj == null) return null;
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 IllegalStateException("Deep clone failed: " + obj.getClass(), e);
}
}
}
// 使用
Student s2 = DeepCloneUtils.deepClone(s1); // 一行代碼搞定任意深度對象圖
| 優點 | 缺點 |
|---|---|
| 零侵入:不修改業務類,僅需實現 Serializable (標記介面) | 性能差:IO 流開銷、反射調用、字節碼編解碼,比 clone 慢 10-100 倍 |
| 自動遞歸:完美處理任意深度對象圖、循環引用、共享引用、final 字段 | 所有類必須 Serializable,第三方庫類無法修改時無法使用 |
| 天然支援跨 JVM 進程傳輸 (RPC、分布式緩存、會話複製) | serialVersionUID 維護負擔,版本演進易報 InvalidClassException |
| 可配合 Externalizable 自定義二進制格式提升性能 | transient 字段不會被拷貝 (需手動處理或放棄 transient) |
// Kryo: 高性能序列化庫,非標準 Java 序列化,需註冊類
Kryo kryo = new Kryo();
kryo.register(Student.class);
kryo.register(Professor.class);
Student s2 = kryo.copy(s1); // 深拷貝,比 Java 序列化快 10 倍+
// FST: 兼容 Java 序列化接口,無需註冊,極快
FSTConfiguration conf = FSTConfiguration.createDefaultConfiguration();
Student s2 = conf.copy(s1);
// Java 16+ Record: 天然不可變,無需深拷貝,直接共享安全
public record Student(String name, int age, Professor advisor) {
// 如需修改某字段產生新對象
public Student withName(String newName) {
return new Student(newName, age, advisor); // advisor 不可變可直接共享
}
}
// Lombok @Value (不可變) + @With (生成 withXxx 方法)
@Value
@With
public class Student {
String name;
int age;
Professor advisor; // 若 Professor 也不可變,整個對象圖安全共享
}
範式轉移:與其費心深拷貝可變對象圖,不如**從源頭設計為不可變對象**。不可變對象天然線程安全、可自由共享、無需拷貝、構造時驗證保證合法狀態。深拷貝僅作為「不得不與可變遺留系統交互」的戰術手段。
需要深拷貝? ├─ 對象圖全是不可變/基本類型 → 直接共享,無需拷貝 ✅ ├─ 能重構為不可變 (Record/@Value) → 重構,消除拷貝需求 ✅ ├─ 必須拷貝可變對象圖: │ ├─ 高性能場景、可控制字段、類可修改 → Cloneable + 手動深拷貝 │ ├─ 通用工具、第三方類、對象圖複雜/未知 → 序列化 (Kryo/FST 推薦) │ └─ 分布式傳輸/持久化 → 序列化 (Protobuf/Avro/JSON 更佳) └─ 單層對象/少量字段 → 手動 new + setter / MapStruct 映射
`Cloneable` 給控制權與性能,`Serializable` 給通用性與便利。但**最好的深拷貝是不需要深拷貝**——擁抱不可變設計,用 Record/@Value 構建值對象,用 `withXxx` 產生新狀態,讓對象圖天然安全共享。遺留系統中,引入 Kryo/FST 替代原生序列化,兼顧性能與零侵入。