默认clone()只做浅拷贝,集合类克隆后仍共享同一实例,修改一个会影响另一个;需对集合字段显式深拷贝,如用新构造、clone()或Stream映射,并递归处理嵌套可变对象。

关键在于:默认 clone() 只做浅拷贝,集合类(如 ArrayList、HashMap)属于引用类型,克隆后两个对象仍共用同一份集合实例 —— 改一个,另一个跟着变。要规避风险,必须对集合成员显式做深拷贝。
为什么集合类在浅拷贝下特别危险
集合本身是对象,不是基本类型或不可变的 String。即使你只改集合里的元素(比如 list.add("new") 或 map.put("k", "v")),原始对象和克隆对象看到的是同一个 ArrayList 实例,内存地址完全一致。
- 浅拷贝后:
person1.addresses == person2.addresses为true - 调用
person2.addresses.add(new Address()),person1.addresses.size()也会增加 - 这不是 bug,是浅拷贝的必然行为
对集合成员做深拷贝的三种可靠方式
核心原则:不能只靠 super.clone(),必须手动为每个可变集合字段创建新实例并复制内容。
-
方式一:构造新集合 + 手动复制元素
适用于所有集合,最直观可控:tmp.addresses = new ArrayList(this.addresses);
(注意:这仅对String、基本类型包装类等不可变元素安全;若元素本身是可变对象,还需递归深拷贝元素) -
方式二:调用集合自身的
clone()(仅限数组和部分集合)ArrayList和ArrayDeque的clone()返回新实例(但仍是浅拷贝其元素):tmp.addresses = (ArrayList) this.addresses.clone(); -
方式三:用 Java 8+ Stream 或构造器初始化
更函数式,语义清晰:tmp.addresses = this.addresses.stream().map(Address::clone).collect(Collectors.toList());
前提是Address类也实现了深拷贝逻辑
别漏掉嵌套的可变引用类型
集合只是“一层”风险。如果集合里存的是自定义对象(如 List),而 Teacher 本身含可变字段(如 private String name 是安全的,但 private List 就不安全),那光深拷贝外层 List 不够,还必须确保 Teacher.clone() 也对其内部集合做了深拷贝。
- 深拷贝是“穿透式”的:每一层可变引用都需独立处理
- 建议给每个含可变引用的类都实现自己的
clone(),并在父类 clone 中调用它 - 例如:
tmp.teacher = (Teacher) this.teacher.clone();
Cloneable 接口本身不解决任何拷贝逻辑
它只是一个“通行证”,没有方法、不参与实现。真正起作用的是你重写的 clone() 方法体 —— 里面写什么,就做什么。
- 没实现
Cloneable→ 运行时抛CloneNotSupportedException - 实现了但
clone()里只写super.clone()→ 默认浅拷贝,集合照旧共享 - 想安全?必须在
clone()方法里,为每个集合字段、每个可变对象字段,补上对应的深拷贝语句










