有效的Java声称elements.clone()就足够了

jus*_*eam 8 java

我正在阅读Joshua Bloch的Effective Java,第2版,第11项:明智地覆盖克隆.

在第56页,他试图解释当我们覆盖clone()某些类(如集合类)时,我们必须复制它的内部.然后他给出了设计课程的例子Stack:

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    public Stack() {...}
    public void push(Object e) {...}
    public Object pop() {...}
    private void ensureCapacity() {...} //omitted for simplicity
}
Run Code Online (Sandbox Code Playgroud)

他声称如果我们只是super.clone()用来克隆a Stack,那么生成的Stack实例"将在其size字段中具有正确的值,但是其elements字段将引用与原始Stack实例相同的数组.修改原始将破坏不变量克隆,反之亦然.您将很快发现您的程序产生无意义的结果或抛出NullPointerException." 现在看起来很公平.但他接着举了一个"正确实施"的例子,这让我很困惑:

@Override public Stack clone() {
    try {
        Stack result = (Stack) super.clone();
        result.elements = elements.clone();
        return result;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}
Run Code Online (Sandbox Code Playgroud)

那现在有什么不同super.clone()?我知道,新的Stack.element将是一个不同于旧的和所有的参考; 但阵列的"内部"仍然是相同的,不是吗?数组的实际元素result.element仍然指向原始Object引用.这仍然可能导致在更改原始文件时破坏克隆的不变量,反之亦然,不是吗?我错过了什么吗?

Mic*_*ael 5

现在与super.clone()有什么不同?

因为阵列现在不同了.如果两个Stack共享同一个数组,那么当一个数组从堆栈中添加或删除时,另一个中的size字段Stack不会更新,从而导致差异.

数组的对象不会自己克隆.这是故意的,因为它们不需要克隆.预计两个Stacks - 或者实际上任何两个Collections - 可以包含对相同对象的引用.您将使用此代码获得相同的行为:

Foo foo = new Foo()
Stack stackOne = new Stack();
Stack stackTwo = new Stack();
stackOne.push(foo);
stackTwo.push(foo);
Run Code Online (Sandbox Code Playgroud)

它本身并不是一个问题,通常是理想的行为.


Swe*_*per 2

你关于如何运作的说法绝对正确clone。不会复制后备数组中的对象,但会复制后备数组。

这不是问题,因为调用者并不期望元素被复制。对于像堆栈这样的集合类,“规范”是进行浅复制。标准库中的一个示例是ArrayList.

另请注意,您也可以通过克隆数组内的对象来实现clone(这意味着堆栈只能存储Clonable公开的对象clone)。这不会破坏 的合同clone。合同非常宽松

  • 顺便说一句,“实现 Cloneable”和“公开 Clone() 方法”是两个独立的要求。您也不能对“Cloneable”类型的引用调用“clone()”。 (2认同)