我正在阅读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引用.这仍然可能导致在更改原始文件时破坏克隆的不变量,反之亦然,不是吗?我错过了什么吗?
现在与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)
它本身并不是一个问题,通常是理想的行为.