浅拷贝只复制引用是因为object.clone()默认逐字段复制,基本类型值照搬,引用类型仅复制地址;深拷贝用序列化虽可避免引用共享,但要求所有字段可序列化且无运行时依赖,性能较差。

浅拷贝为什么只复制了引用?
因为 Object.clone() 默认行为就是逐字段复制:基本类型值照搬,引用类型只复制地址。对象本身没被重建,所以修改副本里的数组或集合,原对象跟着变。
常见错误现象:clone() 后修改 list.add(),原对象的 list 也多了一项;person.getAddress().setCity("Beijing") 同时改了原 person 和克隆体。
- 必须手动重写
clone(),并为每个可变引用字段调用其自身的clone()或新建对象 - 类要实现
Cloneable接口,否则抛CloneNotSupportedException - 注意子类继承父类
clone()时,父类字段是否已正确处理——容易漏掉父类里新增的引用字段
深拷贝用序列化真的安全吗?
序列化拷贝(通过 ObjectOutputStream / ObjectInputStream)能绕过引用共享问题,但前提是所有字段所属类型都可序列化,且不依赖 JVM 运行时状态(比如线程、Socket、数据库连接等)。
使用场景:配置对象、DTO、纯数据载体类;不适合含回调、监听器、Spring 代理对象或 ThreadLocal 字段的类。
立即学习“Java免费学习笔记(深入)”;
- 类必须实现
Serializable,且所有非transient引用字段也要可序列化 -
static和transient字段不会被序列化,拷贝后为默认值(null/0/false) - 性能比
clone()差不少,尤其对象深、字段多时,序列化/反序列化有明显开销 - 某些 JDK 版本对匿名内部类序列化支持不稳定,可能抛
NotSerializableException
第三方库(如 Apache Commons Lang)怎么选?
SerializationUtils.clone() 底层还是走序列化,只是封装了异常和流操作;而 BeanUtils.cloneBean() 是基于反射的浅拷贝,不解决嵌套引用问题——名字带 “clone” 不代表是深拷贝。
容易踩的坑:SerializationUtils.clone() 对不可序列化字段静默失败(返回 null),不抛异常;BeanUtils 遇到同名不同类型的属性直接跳过,也不报错。
- 确认你用的是
org.apache.commons.lang3.SerializationUtils.clone(),不是老版commons-lang的同名方法 - 若字段含
java.time.LocalDateTime等新时间类,需确保 JDK ≥ 8 且序列化机制兼容(它们实现了Serializable) - 不要在循环里频繁调用,避免反复创建
ByteArrayOutputStream和ObjectInputStream
自定义深拷贝要不要手写递归?
手写递归拷贝(比如遍历字段 + 判断类型 + 分支处理)理论上最可控,但实际项目中几乎没人这么做——太容易漏字段、错判类型、陷入循环引用(A→B→A)导致栈溢出。
真正需要定制逻辑的场景极少,比如字段含加密上下文、缓存句柄、或需按业务规则跳过某些字段。这时候更稳妥的做法是:只对关键字段重写 clone(),其余走默认序列化,再组合。
- 如果真要递归,务必加访问标记(如
Set<object></object>记录已拷贝对象)防循环引用 - 优先考虑
copy constructor(构造函数传入原对象)或 builder 模式,语义清晰、IDE 友好、调试方便 - 注意泛型擦除问题:运行时无法知道
List<string></string>和List<integer></integer>的区别,反射取值易出ClassCastException
clone();而手写递归看似彻底,维护成本和出错概率反而更高。










