← 返回题目列表

Java 的 Cloneable 有哪些坑?为什么很多项目不用它?

高频 困难 第 15 / 25 题 更新于 2026/07/28
Cloneableclone原型模式Java深拷贝

简化版

Java 的 Cloneable 只是一个标记接口,本身没有 clone() 方法;真正的 clone() 来自 Object,而且默认是浅拷贝。它还涉及受保护访问权限、异常处理、构造器不执行、深拷贝要手写等问题,所以很多项目更倾向于使用拷贝构造器、静态工厂或显式 copy() 方法。

详细版

Cloneable 常见坑点:

  1. Cloneable 没有定义 clone() 方法;
  2. 不实现 Cloneable 调用 Object.clone() 会抛 CloneNotSupportedException
  3. Object.clone()protected,外部不能直接调;
  4. super.clone() 默认浅拷贝;
  5. 克隆对象不会执行构造器;
  6. 可变引用字段要手动处理;
  7. 继承层次复杂时复制规则容易混乱。

因此,工程代码里经常这样写:

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个可变 ArrayListsuper.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() 或拷贝构造器,把字段复制规则写明白,尤其是可变引用和唯一标识字段。