Java List深克隆失败因clone()和new ArrayList(original)仅浅拷贝,复制引用而非对象;需用序列化、拷贝构造函数或Builder等实现真正深克隆,并注意Serializable限制与字段递归处理。

Java List 深克隆失败时,clone() 和 new ArrayList(original) 为什么不管用
因为它们都只做了浅拷贝:List 容器被复制了,但里面每个元素的引用还是指向原对象。改副本里的对象字段,原 List 里对应对象也会变。
常见错误现象:list2.get(0).setName("xxx") 后,list1.get(0).getName() 也变了;或者序列化反序列化后字段为 null —— 那说明对象本身没实现 Serializable 或者有不可序列化字段。
- 使用场景:需要独立修改副本中对象状态,且不希望影响原始数据(比如编辑前备份、多线程各自处理副本)
-
new ArrayList(original)只复制引用,不是深克隆 -
original.clone()是Object.clone()的默认行为,同样只浅拷贝 - 性能影响:深克隆必然涉及对象遍历和新实例创建,嵌套越深、对象越多,开销越大
用序列化方式实现通用深克隆,但要注意 Serializable 的坑
这是最省心的通用方案,前提是 List 中所有元素及其成员字段都实现了 Serializable,且不含 transient 关键字段(或已正确处理)。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 确保目标类及其所有非基本类型字段所属类都声明了
implements Serializable - 避免在类中持有
Thread、Socket、Connection等不可序列化资源 - 若字段加了
transient,需手动在writeObject/readObject中处理,否则反序列化后为 null - 示例片段:
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(originalList); oos.close(); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); List<MyObj> deepCopy = (List<MyObj>) ois.readObject();
手写 copy constructor 或 toBuilder() 更可控,但要写得多
当你无法控制源类(比如第三方库对象)、或部分字段不该被克隆(如 ID、缓存引用)、或想跳过某些昂贵计算时,硬编码才是唯一可靠路径。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 为每个需要深克隆的类添加
public MyObj(MyObj other)构造函数,内部对每个可变字段递归克隆 - 如果类有 Builder,优先用
other.toBuilder().build(),语义清晰且易维护 - 注意集合字段:不能直接
this.items = other.items,得this.items = new ArrayList(other.items)再逐个克隆元素 - 避免在构造函数里调用重写方法(如
this.init()),防止子类未初始化完成就执行逻辑
用 Lombok + @Builder 配合自定义 toBuilder,减少样板代码
Lombok 的 @Builder(toBuilder = true) 能生成 toBuilder() 方法,但它默认仍是浅拷贝。必须配合 @Singular 和手动字段处理才能真正深克隆集合内对象。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 对集合字段加
@Singular注解,Lombok 会生成带深拷贝逻辑的 builder 方法(但仅限于集合本身,不递归元素) - 仍需在
toBuilder()返回前,对集合中每个元素显式调用其克隆方法,例如:.items(other.getItems().stream().map(MyObj::new).collect(Collectors.toList())) - 不要依赖
@Data自动生成的clone(),它不处理引用类型字段 - 编译时检查是否启用了
-Dlombok.addLombokGeneratedAnnotation=true,避免 IDE 误报“未覆盖 clone”
最常被忽略的一点:深克隆不是“一劳永逸”的操作。如果 List 里混着不同类的对象,或者某对象内部持有了外部服务的回调引用,序列化会直接失败,而手写克隆又容易漏字段——这时候得按类型分组处理,甚至引入运行时类型检查 + 策略映射。








