
clone() 方法为什么经常抛 CloneNotSupportedException
因为 Object.clone() 是受保护的,且默认只支持浅拷贝;类没实现 Cloneable 接口时,调用会直接抛这个异常——不是忘了 catch,而是根本没资格 clone。
- 必须显式声明
implements Cloneable,否则 JVM 拒绝克隆(哪怕只是空接口) -
clone()是 native 方法,不走构造器,字段值原样复制,引用类型地址照搬 - 子类重写时得调
super.clone(),否则可能拿到Object的原始实现,导致类型错误 - 如果父类没正确实现
clone(),子类即使写了也会崩,常见于继承了未适配克隆的第三方类
深克隆怎么写才不出错:手动重写 vs 序列化
手动深克隆要逐字段判断是否可变、是否需递归 clone;序列化靠 JVM 自动处理引用关系,但要求所有字段所属类型都可序列化。
- 手动方案:在
clone()里对每个引用字段调用其clone()或新建副本,比如new ArrayList(this.items),但容易漏字段或误用浅拷贝 - 序列化方案:用
ObjectOutputStream写入字节数组再反序列化,绕过构造器和字段访问控制,天然深克隆 - 注意
transient字段会被跳过,序列化方案下它们变成 null,手动方案里你得自己决定要不要恢复 - 性能上,序列化比手动 clone 慢 5–10 倍,尤其对象大或嵌套深时,别在高频路径用
Serializable 克隆的兼容性陷阱
序列化克隆依赖 serialVersionUID 和类结构稳定性,改了字段、删了方法、甚至加了注解都可能导致反序列化失败。
- 没显式定义
serialVersionUID,JVM 会自动生成,但只要类结构微调(比如加个static字段),ID 就变,反序列化直接抛InvalidClassException -
static和transient字段不参与序列化,克隆后这些字段是默认值,不是原对象的快照 - 某些类(如
java.time.LocalDateTime)实现了Serializable,但内部用了不可序列化的组件,实际运行仍可能报NotSerializableException - Android 上默认禁用 Java 序列化,
Parcelable才是正解,硬上Serializable会 crash
什么时候该放弃 clone,换别的方案
当对象含回调、线程、IO 句柄、单例引用,或者字段类型本身不支持克隆时,clone() 和序列化都会失效,强行搞只会埋雷。
立即学习“Java免费学习笔记(深入)”;
- 含
ThreadLocal、ExecutorService、数据库连接等资源型字段,克隆没意义,应该用工厂方法或 builder 重建 - 用了 Lombok 的
@Data,它默认不生成clone(),加@AllArgsConstructor+ 手动写 clone 也难保字段覆盖全 - 字段里有匿名内部类或 lambda,它们隐式持有了外部类引用,序列化时可能意外把整个上下文拖进去,内存暴涨
- 真正需要“副本”的场景,往往更适合用不可变对象(
record或final字段 + builder),从源头避免克隆需求
transient 字段、一行没写的 serialVersionUID、一次没检查的第三方类实现,都可能让克隆在某个深夜的压测里突然断掉。






