浅拷贝只复制对象本身而不复制引用指向的堆内存,导致新旧对象共享引用对象;深拷贝需递归复制所有层级引用,确保完全独立。

浅拷贝只复制对象本身,不复制引用指向的堆内存
当你调用 Object.clone() 且该类没重写 clone() 或没实现 Cloneable,会抛出 CloneNotSupportedException。即使成功,默认行为是浅拷贝:基本类型字段值被复制,引用类型字段只复制地址,新旧对象共享同一堆内存中的对象。
常见错误现象:修改克隆后对象的某个 List 元素,原对象也跟着变;toString() 输出看似不同,但 == 比较引用字段返回 true。
- 必须显式实现
Cloneable接口(仅作标记,无方法) -
protected的clone()需改为public并处理异常 - 对每个可变引用字段(如
ArrayList、自定义对象),要手动调用其clone()或新建实例并复制内容
深拷贝要递归复制所有层级的引用对象
深拷贝的目标是让克隆对象与原对象完全独立,任意一方修改内部状态都不影响另一方。没有语言级内置支持,必须手动实现或借助工具。
使用场景包括:缓存中返回对象副本避免污染、多线程间安全传递可变对象、测试中隔离 fixture 状态。
立即学习“Java免费学习笔记(深入)”;
- 手动实现:重写
clone(),对每个引用字段 new 一个新对象,并递归调用其clone()(要求它们也支持) - 序列化方式(如
ObjectOutputStream+ByteArrayInputStream):要求所有字段类型都实现Serializable,且注意transient字段丢失、性能差、无法处理循环引用 - JSON 序列化(如 Jackson):简单但有类型擦除风险(泛型信息丢失)、不支持非 public 字段、忽略
transient和静态字段
为什么 Arrays.copyOf() 和 new ArrayList(list) 不是深拷贝
这些操作常被误认为“深拷贝”,其实只是对容器本身做了浅层复制 —— 新建了数组或 ArrayList 实例,但其中元素仍是原引用。
String[] arr1 = {"a", "b"};
String[] arr2 = Arrays.copyOf(arr1, arr1.length);
arr2[0] = "x"; // ✅ arr1[0] 还是 "a",String 不可变,看不出来
List list1 = Arrays.asList(new StringBuilder("hello"));
List list2 = new ArrayList<>(list1);
list2.get(0).append("!"); // ❌ list1.get(0) 也会变成 "hello!"
-
Arrays.copyOf()复制的是数组引用本身,不是元素内容 -
new ArrayList(collection)调用的是addAll(),本质是遍历赋值引用 - 只有元素是不可变对象(如
String、Integer)时,浅拷贝“看起来”像深拷贝
面试中容易被追问的边界点
面试官常从实现细节切入,比如:如果对象里有 final 字段、有 ThreadLocal、含 Lambda 表达式、或继承自第三方类无法修改源码,怎么办?
-
final引用字段在浅拷贝中无法重新赋值,必须在构造时初始化,深拷贝需通过反射绕过(不推荐)或改用工厂方法 -
ThreadLocal是线程绑定的,不应被拷贝;若强行序列化会失效,应明确设计为“不参与拷贝” - Lambda 表达式编译后是私有静态方法+捕获变量,序列化可能失败;建议避免在需深拷贝的对象中持有 Lambda
- 无法修改父类时,子类
clone()中对父类引用字段的深拷贝逻辑必须小心:不能访问super.clone()返回对象的私有字段
真正难的不是写出一个能跑的 clone 方法,而是判断哪些字段必须深拷、哪些可以共享、哪些根本不该拷贝 —— 这取决于业务语义,不是技术规则能覆盖的。










