CodeQL 文档

无克隆方法

ID: java/missing-clone-method
Kind: problem
Security severity: 
Severity: error
Precision: medium
Tags:
   - reliability
   - maintainability
Query suites:
   - java-security-and-quality.qls

单击以在 CodeQL 存储库中查看查询

实现 Cloneable 的类应覆盖 Object.clone。对于非平凡对象,Cloneable 合约要求对对象的内部状态进行深度复制。没有 clone 方法的类表示该类违反了合约,并且将产生不良行为。

Java API 规范指出,对于对象 xclone 方法的一般目的是满足以下三个属性

  • x.clone() != x(克隆对象是不同的对象实例)

  • x.clone().getClass() == x.getClass()(克隆对象与源对象类型相同)

  • x.clone().equals(x)(克隆对象与源对象具有相同的“内容”)为了使克隆对象与源对象具有相同的类型,非 final 类必须调用 super.clone,并且该调用最终必须到达 Object.clone,它会创建一个正确类型的实例。如果它使用构造函数创建一个新对象,则未实现 clone 方法的子类将返回错误类型的对象。此外,所有也重写 clone 的类的超类都必须调用 super.clone。否则,它永远不会到达 Object.clone,并且会创建一个不正确的类型对象。

但是,由于 Object.clone 只对对象字段进行浅层复制,因此任何具有“深层结构”(例如,使用数组或 Collection 的对象)的 Cloneable 对象都必须获取由对 super.clone 的调用产生的克隆,并将显式创建的结构副本分配给克隆的字段。这意味着克隆实例不与其源对象共享其内部状态。如果它确实共享其内部状态,则在克隆对象中进行的任何更改也会影响源对象的内部状态,这可能会导致意外的行为。

一个额外的复杂之处在于,clone 无法修改 final 字段中的值,而这些值已经由对 super.clone 的调用设置。必须将某些字段设为非 final,才能正确实现 clone 方法。

建议

创建对象内部状态的深度副本的必要性意味着,对于大多数对象而言,clone 必须被重写以满足 Cloneable 契约。实现一个 clone 方法,正确创建克隆对象的内部状态。

此建议的显着例外是

  • 仅包含基本类型的类(只要其 Cloneable 超类型全部调用 super.cloneObject.clone 就会正确克隆这些类型)。

  • 不引入新状态的 Cloneable 类的子类。

示例

在以下示例中,WrongStack 未实现 clone。这意味着当 ws1clonews1 克隆时,将使用默认的 clone 实现。这会导致对 ws1clone 栈的操作影响 ws1 栈。

abstract class AbstractStack implements Cloneable {
    public AbstractStack clone() {
        try {
            return (AbstractStack) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError("Should not happen");
        }
    }
}

class WrongStack extends AbstractStack {
    private static final int MAX_STACK = 10;
    int[] elements = new int[MAX_STACK];
    int top = -1;

    void push(int newInt) {
        elements[++top] = newInt;
    }
    int pop() {
        return elements[top--];
    }
    // BAD: No 'clone' method to create a copy of the elements.
    // Therefore, the default 'clone' implementation (shallow copy) is used, which
    // is equivalent to:
    //
    //  public WrongStack clone() {
    //      WrongStack cloned = (WrongStack) super.clone();
    //      cloned.elements = elements;  // Both 'this' and 'cloned' now use the same elements.
    //      return cloned;
    //  }
}

public class MissingMethodClone {
    public static void main(String[] args) {
        WrongStack ws1 = new WrongStack();              // ws1: {}
        ws1.push(1);                                    // ws1: {1}
        ws1.push(2);                                    // ws1: {1,2}
        WrongStack ws1clone = (WrongStack) ws1.clone(); // ws1clone: {1,2}
        ws1clone.pop();                                 // ws1clone: {1}
        ws1clone.push(3);                               // ws1clone: {1,3}
        System.out.println(ws1.pop());                  // Because ws1 and ws1clone have the same
                                                        // elements, this prints 3 instead of 2
    }
}

在以下修改后的示例中,RightStack 确实实现了 clone。这意味着当 rs1cloners1 克隆时,对 rs1clone 栈的操作不会影响 rs1 栈。

abstract class AbstractStack implements Cloneable {
    public AbstractStack clone() {
        try {
            return (AbstractStack) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError("Should not happen");
        }
    }
}

class RightStack extends AbstractStack {
    private static final int MAX_STACK = 10;
    int[] elements = new int[MAX_STACK];
    int top = -1;

    void push(int newInt) {
        elements[++top] = newInt;
    }
    int pop() {
        return elements[top--];
    }

    // GOOD: 'clone' method to create a copy of the elements.
    public RightStack clone() {
        RightStack cloned = (RightStack) super.clone();
        cloned.elements = elements.clone();  // 'cloned' has its own elements.
        return cloned;
    }
}

public class MissingMethodClone {
    public static void main(String[] args) {
        RightStack rs1 = new RightStack();              // rs1: {}
        rs1.push(1);                                    // rs1: {1}
        rs1.push(2);                                    // rs1: {1,2}
        RightStack rs1clone = rs1.clone();              // rs1clone: {1,2}
        rs1clone.pop();                                 // rs1clone: {1}
        rs1clone.push(3);                               // rs1clone: {1,3}
        System.out.println(rs1.pop());                  // Correctly prints 2
    }
}

参考

  • J. Bloch,Effective Java(第二版),第 11 项。Addison-Wesley,2008 年。

  • Java API 规范:Object.clone()

  • ©GitHub, Inc.
  • 条款
  • 隐私