object.clone()默认是浅克隆:只复制基本类型字段和引用地址,不复制引用对象本身,导致新旧对象共享同一堆内存中的引用类型数据。

为什么 clone() 方法默认不是深克隆
Java 的 Object.clone() 是浅拷贝:它只复制对象本身字段,对引用类型字段(比如 List、Map、自定义对象)仅复制引用地址,新旧对象共享同一堆内存。一旦修改副本里的集合内容,原对象也会“意外”改变。
常见错误现象:NullPointerException 或数据污染 —— 比如你克隆了一个含 ArrayList 的配置类,往副本里 add() 元素,结果原配置也多了一条。
- 必须手动重写
clone()并对每个引用字段调用其clone()或新建实例 - 所有被引用的类都得实现
Cloneable,否则抛CloneNotSupportedException - 字段是
final时无法在clone()中重新赋值,直接失败
用序列化实现深克隆最简单但有硬限制
把对象写成字节流再读回来,天然绕过引用共享问题,是事实上的“无脑深克隆”。但它要求整个对象图里**所有类及其字段类型都可序列化**。
典型报错:java.io.NotSerializableException: com.example.MyClass —— 只要链路上任一环节没加 implements Serializable,就崩。
立即学习“Java免费学习笔记(深入)”;
- 非
static、非transient字段必须可序列化;含 Lambda、匿名内部类、线程局部变量(ThreadLocal)的对象基本不可用 - 性能差:IO + 反射开销大,比手写
clone()慢 5–10 倍,高频调用场景慎用 - 注意
serialVersionUID不一致可能引发InvalidClassException,尤其跨版本部署时
示例片段:
public static <T extends Serializable> T deepCopy(T obj) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (T) ois.readObject();
}
第三方库(如 Apache Commons Lang)能省事但引入依赖风险
SerializationUtils.clone() 就是上面序列化方案的封装,用起来干净,但本质没绕过序列化的缺陷。
另一个选择是 BeanUtils.cloneBean(),它靠反射逐字段复制,不依赖 Serializable,但默认仍是浅拷贝 —— 除非你配合自定义 Converter 处理集合和嵌套对象,工作量不比手写少。
- Apache Commons Lang 3.12+ 对泛型支持更好,但
clone()方法仍要求目标类无参构造且字段可访问 - 若项目已用 Spring,
BeanUtils.copyProperties()更常用,但它不是克隆,而是“复制到新实例”,需提前 new 对象 - 注意
BeanUtils不处理循环引用,遇到父子双向关联会栈溢出
真正可控的深克隆:构造函数 + 手动复制(推荐用于核心模型)
当对象结构稳定、字段不多、且对性能/可靠性要求高时,显式构造是最透明、最易调试的方式。它不依赖 JVM 特性或外部约束,连 final 字段也能正确初始化。
使用场景:DTO、Entity、配置类等生命周期明确、变更不频繁的类。
- 为每个需要深克隆的类添加带参数的构造函数,接收同类型对象,内部对每个字段做对应处理
- 集合字段用
new ArrayList(other.list)或other.list.stream().map(...).collect(...) - 嵌套对象字段必须调用其自身的拷贝构造或
copy()方法,不能漏层级 - 如果类有 builder 模式,可在
build()前 clone 所有输入参数,避免 builder 内部缓存引用
示例:
public class User {
private final String name;
private final List<Role> roles;
public User(User other) {
this.name = other.name; // String 不用深拷,不可变
this.roles = other.roles != null
? other.roles.stream().map(Role::new).collect(Collectors.toList())
: null;
}
}
深克隆没有银弹。序列化最快上手但脆弱;clone() 接口最轻量但容易漏字段;构造函数最稳但要写更多代码——选哪个,取决于你愿不愿意为“确定性”多花十分钟。










