clone() 默认只做浅拷贝,因其底层用 native 方法直接复制内存字节,不区分字段类型,所有引用字段仅复制地址值,导致原始与克隆对象共享堆内存中的嵌套对象。

为什么 clone() 默认只做浅拷贝
因为 Object.clone() 底层是用 native 方法直接复制内存字节,它不关心字段类型,所有引用类型字段都只是把地址值复制了一份。原始对象和克隆对象里的 ArrayList、StringBuffer、自定义对象等,指向的是同一块堆内存。
常见错误现象:original.list.add("x") 后,copy.list 也多了一个元素;修改 original.nestedObj.field,copy.nestedObj.field 跟着变。
- 必须让类实现
Cloneable接口,否则调用clone()会抛CloneNotSupportedException -
clone()是protected方法,子类需显式覆写为public - 不能依赖构造器逻辑,
clone()不走构造函数,字段初始化代码不会执行
手动实现深拷贝的三种可行路径
没有银弹,选哪种取决于对象结构、性能要求和第三方依赖约束。
场景:对象含嵌套集合、数组、非 final 引用字段,且不允许序列化(比如含 ThreadLocal 或 Socket)。
立即学习“Java免费学习笔记(深入)”;
- 逐字段手动
new+ 拷贝:最可控,适合字段少、结构稳定;注意数组要用Arrays.copyOf(),集合要新建并addAll() - 用
SerializationUtils.clone()(Apache Commons Lang):要求所有字段可序列化,性能较差,但一行搞定;注意transient字段会被跳过 - JSON 序列化反序列化(如 Jackson):绕过
Serializable限制,但会丢失类型信息(比如HashMap变成LinkedHashMap),且无法处理循环引用
Cloneable 的陷阱:为什么它不是接口该有的样子
它不声明任何方法,纯靠 JVM 特殊识别;实现它不等于获得克隆能力,只是“解除了抛异常的权限”。很多开发者误以为 implements 就完事了,结果运行时报错或行为诡异。
- 子类覆写
clone()时,若调用了super.clone(),父类字段被浅拷,子类新增引用字段也要自己深拷 - 如果父类没实现
Cloneable,子类即使实现了也没用,super.clone()仍会抛异常 - 不可变类(字段全
final且类型不可变)不需要克隆,返回this即可,但要注意别破坏封装
替代方案比死磕 clone() 更实际
Java 标准库中,clone() 被长期诟病设计失败;现代项目里,更推荐明确、可读、易测的方式。
- 提供带参数的构造器:如
new Person(original.getName(), original.getAge()),天然隔离、语义清晰 - 用 builder 模式:尤其适合字段多的对象,builder 本身可复用,还能做校验
- 用 record(Java 14+):自动具备值语义,配合
with风格方法或新实例构造,比clone()更安全
真正难的不是怎么克隆,而是厘清「这个对象是否真的需要被克隆」——如果只是传参或临时持有,不可变性或防御性拷贝(如 new ArrayList(list))往往更轻量、更可靠。










