"clone() 默认是浅拷贝"这句话人人都知道,但真正踩过坑的人才知道"浅"到什么程度。这篇真实用一个带数组、集合、字符串字段的类,一项一项验证 clone() 之后哪些字段是独立的、哪些是共享的。
用 super.clone() 默认实现,对比基本类型对应的包装/String字段、数组字段、集合字段克隆后的真实引用关系。
CloneShallowDemo.java class CloneShallowDemo implements Cloneable { int[] arr = {1, 2, 3}; List<String> list = new ArrayList<>(Arrays.asList("a", "b")); String name = "original"; public CloneShallowDemo clone() { return (CloneShallowDemo) super.clone(); } }
original == copy: false original.name == copy.name (String不可变,共享无所谓): true original.arr == copy.arr (数组字段): true original.list == copy.list (集合字段): true
真实结果验证了 super.clone() 的默认行为:它真实创建了一个新对象(original == copy 为 false),但对每个字段只做了"按位复制"——对于引用类型字段,复制的是引用本身(内存地址),不是引用指向的对象。name 字段虽然也是共享的引用,但因为 String 不可变,共享并不会引发问题;而 arr 和 list 这两个可变对象的引用被真实共享,才是浅拷贝真正危险的地方。
直接修改 copy 的数组元素和集合内容,观察 original 是否真实跟着变化。
修改 copy.arr[0]=999 后,original.arr[0] = 999 修改 copy.list.add(c) 后,original.list = [a, b, c]
在 clone() 方法内部,对数组字段再手动调用一次 clone(),验证这样是否真的能让两个对象互不影响。
CloneShallowDemo.java static class DeepDemo implements Cloneable { int[] arr = {1, 2, 3}; public DeepDemo clone() { DeepDemo d = (DeepDemo) super.clone(); d.arr = arr.clone(); // 手动深拷贝数组字段 return d; } }
深拷贝版本 d1.arr == d2.arr: false 修改 d2.arr[0]=999 后,d1.arr[0] = 1(深拷贝后互不影响)
真实结果证明了正确的做法:在 clone() 方法内部,对每一个可变引用类型字段手动调用一次它自己的 clone()(数组的 clone() 本身就是深拷贝一层),让 d1.arr 和 d2.arr 真实变成两个独立的数组对象。这一次修改 d2.arr[0],d1.arr[0] 真实保持不变——这也是很多 IDE 自动生成的 clone() 只做了 super.clone() 却没有处理引用字段,导致"看起来实现了 Cloneable,实际上是个陷阱"的根本原因。
用 identityHashCode 给每个数组/集合对象编号,串成动画:编号相同就是同一个对象,共享;编号不同就是两个独立对象,互不影响。