在 Java 中,賦值傳遞的是引用,淺拷貝僅複製頂層對象及基本類型字段,引用類型字段仍指向同一堆內存對象。當業務場景需要完全隔離的對象副本時(如快照模式、命令模式、會話狀態保存),必須實現深度克隆。
利用 Java 序列化機制將對象圖寫入字節流,再反序列化重建對象圖。由於反序列化會在堆上創建全新對象並遞歸復制所有可達對象,從而實現真正的深度克隆。
public static <T extends Serializable> T deepClone(T obj) {
if (obj == null) return null;
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis)) {
oos.writeObject(obj);
oos.flush();
@SuppressWarnings("unchecked")
T cloned = (T) ois.readObject();
return cloned;
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("深度克隆失敗: " + e.getMessage(), e);
}
}
import java.io.*;
import java.util.*;
// 被克隆的對象必須實現 Serializable
class Address implements Serializable {
private String city;
private String street;
// 構造器、getter/setter、toString 省略
}
class User implements Serializable {
private String name;
private int age;
private Address address; // 引用類型
private List<String> tags; // 集合類型
private Map<String, Object> meta; // Map 類型
private transient String token; // transient 不參與序列化
// 構造器、getter/setter、toString 省略
}
public class DeepCloneDemo {
public static void main(String[] args) {
// 構建原始對象
User original = new User();
original.setName("張三");
original.setAge(28);
original.setAddress(new Address("北京", "朝陽區"));
original.setTags(Arrays.asList("Java", "架構師"));
original.setMeta(Map.of("level", "senior", "dept", "研發"));
original.setToken("secret-token"); // 不會被克隆
// 執行深度克隆
User cloned = deepClone(original);
// 驗證克隆效果
System.out.println("=== 克隆前 ===");
System.out.println(original);
System.out.println("\n=== 克隆後 ===");
System.out.println(cloned);
// 修改克隆對象的引用類型字段
cloned.getAddress().setCity("上海");
cloned.getTags().add("Go");
cloned.getMeta().put("skill", "微服務");
System.out.println("\n=== 修改克隆對象後 ===");
System.out.println("原對象地址: " + original.getAddress().getCity()); // 仍為北京
System.out.println("原對象標簽: " + original.getTags()); // 未受影響
System.out.println("原對象元數據: " + original.getMeta()); // 未受影響
System.out.println("克隆對象 token: " + cloned.getToken()); // 為 null (transient)
}
// 通用深度克隆方法 (同上)
}
SerializableserialVersionUID 避免版本不兼容private transient String password; // 克隆後為 null
private transient Logger logger; // 克隆後為 null,需手動重新初始化
對於不應序列化或無法序列化的字段(線程、連接、日誌器、密碼),使用 transient 並在克隆後手動恢復。
序列化/反序列化會創建新實例,破壞單例。解決方案:
// 方法一:實現 readResolve()
private Object readResolve() {
return INSTANCE; // 返回單例實例
}
// 方法二:使用枚舉實現單例 (推薦,天然防序列化破壞)
public enum Singleton { INSTANCE; }
若引用了無法修改的第三方類,可採用以下策略:
transient,克隆後重新設置writeObject/readObject 手動處理| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 序列化 (本文) | 通用、零侵入、支持複雜對象圖 | 性能差、需 Serializable、單例破壞 | 低頻業務、狀態快照、對象圖深度未知 |
| 拷貝構造器/工廠方法 | 性能高、可控、無依賴 | 代碼冗長、維護成本高、易漏字段 | 核心領域模型、高性能要求 |
| JSON 序列化 (Jackson/Gson) | 可讀性強、跨語言、無需 Serializable | 性能中等、需無參構造器、臨時字段丟失 | 配置對象、DTO、與前端交互 |
| MapStruct/Dozer/BeanUtils | 自動映射、性能尚可 | 深度克隆支持有限、需配置 | 層間對象轉換 (DTO↔Entity) |
| Kryo/FST | 極快、體積小、支持非 Serializable | 需引入第三方依賴 | 高性能序列化、緩存、分布式傳輸 |
// 依賴: com.fasterxml.jackson.core:jackson-databind
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 深度克隆
User cloned = mapper.convertValue(original, User.class);
// 或
User cloned2 = mapper.readValue(mapper.writeValueAsBytes(original), User.class);
深度克隆看似簡單,實則暗藏序列化陷阱、性能陷阱、單例陷阱。根據業務場景選擇合適方案,並在代碼審查中建立檢查清單,才能避免線上「莫名其妙的數據污染」Bug。