Java 的 Cloneable 有哪些坑?为什么很多项目不用它?
简化版
Java 的 Cloneable 只是一个标记接口,本身没有 clone() 方法;真正的 clone() 来自 Object,而且默认是浅拷贝。它还涉及受保护访问权限、异常处理、构造器不执行、深拷贝要手写等问题,所以很多项目更倾向于使用拷贝构造器、静态工厂或显式 copy() 方法。
详细版
Cloneable 常见坑点:
Cloneable没有定义clone()方法;- 不实现
Cloneable调用Object.clone()会抛CloneNotSupportedException; Object.clone()是protected,外部不能直接调;super.clone()默认浅拷贝;- 克隆对象不会执行构造器;
- 可变引用字段要手动处理;
- 继承层次复杂时复制规则容易混乱。
因此,工程代码里经常这样写:
User copy = new User(source);
或:
User copy = source.copy();
这种方式更直观,也更容易表达业务复制语义。
完整版教学
一、Cloneable 是标记接口
Cloneable 的定义很特别,它没有方法:
public interface Cloneable {
}
它只是告诉 Object.clone():这个对象允许被克隆。
如果一个类没有实现 Cloneable,调用 super.clone() 会抛异常。
二、clone 方法来自 Object
clone() 方法定义在 Object 中:
protected native Object clone() throws CloneNotSupportedException;
它是 protected,不是 public。
所以如果你希望外部能调用复制方法,需要在自己的类里重写并放宽访问权限:
@Override
public User clone() {
// ...
}
这个设计让 Cloneable 用起来不够直观。
三、super.clone 默认是浅拷贝
例如:
class User implements Cloneable {
private List<String> roles;
public User clone() {
try {
return (User) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
}
这里复制出来的新 User 和原对象共享同一个 roles 列表。
如果 roles 可变,就必须手动复制:
User copy = (User) super.clone();
copy.roles = new ArrayList<>(this.roles);
return copy;
四、克隆不会执行构造器
clone() 创建对象时不会执行构造器逻辑。
如果构造器里有重要初始化:
public User() {
this.id = UUID.randomUUID().toString();
}
克隆时不会自动重新生成 ID。
这意味着一些字段可能被原样复制,导致业务上不正确。
五、继承场景下更复杂
如果父类和子类都有可变字段,每一层都要正确处理复制逻辑。
否则可能出现:
- 父类字段深拷贝了,子类字段没处理;
- 子类忘记调用
super.clone(); - 返回类型转换混乱;
- 复制规则分散在多个层级。
继承越深,clone() 越难维护。
六、为什么推荐 copy 或拷贝构造器
显式 copy() 更容易读:
class User {
User copy() {
User copy = new User();
copy.name = this.name;
copy.roles = new ArrayList<>(this.roles);
return copy;
}
}
拷贝构造器也很清楚:
User(User source) {
this.name = source.name;
this.roles = new ArrayList<>(source.roles);
}
它们能明确表达每个字段怎么处理,也更贴近业务语义。
七、常见误区与追问
Cloneable 的行为要通过对象图验证,而不能只看顶层引用不同。若 Order 含1个可变 ArrayList,super.clone() 后两个 Order 不同,但它们默认仍指向同一个列表;任一副本添加元素都会影响另一方。并且 Cloneable 没声明 clone(),公开复制能力仍需子类覆盖 Object.clone()。
| 检查维度 | 判定依据 |
|---|---|
| 顶层对象 | super.clone() 创建新实例 |
| 引用字段 | 默认只复制引用,仍可能共享 |
易错点:实现 Cloneable 只获得“允许 Object.clone”的标记,不等于自动深拷贝。
- 误区:Cloneable 接口声明了 clone 方法。 它是空的标记接口,方法实际定义在 Object 中且为 protected。
- 追问:不实现 Cloneable 直接调用 super.clone 会怎样? 运行时抛出
CloneNotSupportedException。 - 误区:clone 会调用目标类构造器。 Object 的克隆机制直接复制对象状态,不走普通构造流程。
- 追问:final 引用字段能否在 clone 后替换? 不能在常规实例方法中重新赋值,深拷贝设计会因此更棘手。
- 追问:现代 Java 更推荐什么? 显式 copy 方法、拷贝构造器或不可变值对象通常契约更清楚。
八、加强记忆
Cloneable 的坑在于它只是标记接口,clone() 来自 Object,默认浅拷贝且不会执行构造器。工程里更推荐显式 copy() 或拷贝构造器,把字段复制规则写明白,尤其是可变引用和唯一标识字段。