Java 每日一題:clone 淺拷貝導致監護人對象共享 Bug 復現與修復
題目代碼
public class Ques1209 {
public static void main(String[] args) {
People guardian = new People("張三");
People son1 = new People("兒子1", guardian);
People son2 = son1.clone(); // 淺拷貝
son2.setName("兒子2");
son1.getGuarder().setName("李四"); // 修改共享對象
System.out.println(son1.getName() + "的監護人是:" + son1.getGuarder().getName());
System.out.println(son2.getName() + "的監護人是:" + son2.getGuarder().getName());
}
}
class People implements Cloneable {
private String name;
private People guarder;
// 構造器、getter/setter 省略
@Override
public People clone() {
try { return (People) super.clone(); }
catch (Exception e) { throw new RuntimeException(e); }
}
}
運行結果
兒子1的監護人是:李四
兒子2的監護人是:李四
核心問題
super.clone() 執行**淺拷貝**:基本類型複製值,引用類型**複製引用地址**。
son1 與 son2 的 guarder 指向**同一個 People 對象**(張三/李四)。
- 修改任一兒子的監護人名稱,**另一個兒子同步變化**。
內存圖解
son1 ──────► guarder ──────► People("李四")
▲
son2 ───────────┘
修復方案:深拷貝
@Override
public People clone() {
try {
People p = (People) super.clone();
// 關鍵:手動拷貝引用類型字段
if (this.guarder != null) {
p.guarder = this.guarder.clone(); // 遞歸深拷貝
}
return p;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
修復後結果
兒子1的監護人是:李四
兒子2的監護人是:張三
深拷貝三大實現方式對比
| 方式 | 優點 | 缺點 | 適用場景 |
| 重寫 clone + 手動拷貝 | 可控、性能好 | 代碼繁瑣、易漏字段 | 字段少、對象圖簡單 |
| 序列化反序列化 | 通用、自動深拷貝 | 需實現 Serializable、性能差、不支持 transient | 通用工具類、對象圖複雜 |
| 第三方庫 (Apache Commons Lang / Hutool) | 一行代碼、處理循環引用 | 引入依賴、反射性能損耗 | 項目已引入工具包 |
最佳實踐建議
- 優先不可變對象:
guarder 設計為不可變,根除共享風險。
- 防御性拷貝:Getter 返回拷貝、Setter 存入拷貝(
this.guarder = new People(other.getName()))。
- 文檔標註:類註釋聲明「可變/不可變、淺/深拷貝語義」。
- 單元測試守護:測試
clone() 後修改原對象不影響副本。
clone() 看似簡單,**淺拷貝是默認陷阱**,深拷貝需顯式編碼。這道題考察的正是對象圖拷貝語義的深度理解。
- 上一篇文章: MyBatis 分頁插件 PageHelper 實戰:零侵入、多數據庫、豐富元數據
- 下一篇文章: JavaMail NoClassDefFoundError: LineInputStream 根因與一鍵修復
Copyright ©
程式碼之家 All Rights Reserved