Cloneable 介面 (標記介面,無方法) 才能呼叫 clone()Object.clone() 為 protected native,需覆寫為 public 才可外部呼叫Cloneable 拋出 CloneNotSupportedExceptionpublic class CloneableObj implements Cloneable {
private String name;
private int score;
@Override
public Object clone() throws CloneNotSupportedException {
CloneableObj result = (CloneableObj) super.clone(); // 淺複製
// 可在此自訂欄位處理
result.name = "New Name";
result.score = 90;
return result;
}
}
| 類型 | 行為 | 適用場景 |
|---|---|---|
| 淺複製 | 基本型別複製值,參考型別複製引用 (共用同一物件) | 不可變物件、無巢狀可變物件 |
| 深複製 | 遞歸複製所有可變參考型別欄位 | 含可變集合、自訂物件圖 |
目標:支援指定複製深度 (如 depth=2 只複製前兩層),容器類型 (List/Map) 始終深度複製。
public Object deepClone(Object obj, int depth) {
Object result = obj;
// 1. 檢查複製條件:非 null、實作 Cloneable、非基本型別/不可變類型/String/Transient/不可存取
if (!isCloneable(obj)) return result;
// 2. 執行複製 (可插入自訂複製策略)
result = doClone(obj);
// 3. 取得需遞歸處理的欄位 (可插入欄位掃描策略:反射/Getter/註解)
Field[] fields = getCloneableFields(obj);
for (Field field : fields) {
Object fieldValue = field.get(obj);
Object clonedValue;
if (depth <= 1) {
// 僅複製容器類型
if (isContainer(fieldValue)) {
clonedValue = deepClone(fieldValue, 1);
} else continue;
} else {
clonedValue = deepClone(fieldValue, depth - 1);
}
// 4. 將複製結果寫回目標物件
field.set(result, clonedValue);
}
return result;
}
isCloneable():排除 primitive、String、Enum、不可變類、transient 欄位、未實作 Cloneable、!field.isAccessible()isContainer():Collection、Map、陣列等容器類型需深度複製doClone():預設呼叫 super.clone(),可替換為序列化/建構子/工廠等策略getCloneableFields():反射掃描 private 欄位需 setAccessible(true)需求:物件圖 A→B→C→...→X,僅需深度複製 X 節點,其餘保持引用共享。
解法:在 doClone() 或欄位遍歷中引入「複製路徑規則引擎」 (如正則匹配欄位路徑、註解標記),動態決定該欄位是深複製、淺複製或共用引用。
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 手寫 clone() | 效能最好、完全可控 | 維護成本高、易漏欄位 | 核心領域模型、高頻呼叫 |
| 反射遞歸深複製 | 通用、一次寫到處用 | 效能較差、需處理循環引用 | 通用工具類、測試資料構建 |
| 序列化/反序列化 | 自動處理完整物件圖 | 需實作 Serializable、效能最差 | 跨 JVM 傳輸、快照備份 |
| JSON 轉換 (Jackson/Gson) | 人類可讀、支援部分欄位 | 需無參建構子、效能中等 | API 回傳、配置物件複製 |
| MapStruct/Dozer | 編譯期生成/映射配置 | 引入依賴、學習曲線 | DTO/Entity 互轉、複雜映射 |
deepClone(obj, depth)Clone 是雙面刃,善用可簡化物件圖構建,濫用則引入隱性 Bug。建立團隊統一複製策略,比單獨掌握技術細節更重要。