我正在阅读Joshua Bloch的 Effective Java,第二版,第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
}
他声称,如果我们仅使用super.clone()
来克隆Stack
,则生成的Stack实例“在其size字段中将具有正确的值,但其element字段将指向与原始的Stack实例。修改原始的实例将破坏克隆中的不变性,反之亦然。您将很快发现您的程序产生了荒谬的结果或抛出了NullPointerException。”
现在,这似乎很公平。但是他随后给出了“正确实现”的示例,这使我感到困惑:
@Override public Stack clone() {
try {
Stack result = (Stack) super.clone();
result.elements = elements.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
现在与super.clone()
有何不同?我知道,新的Stack.element
将与旧的和所有的引用不同。但是数组的“内部”还是一样的,不是吗?数组result.element
的实际元素仍指向原始Object
引用。更改原始副本时,这仍然可能导致破坏克隆的不变性,反之亦然,不是吗?我有什么想念的吗?
答案 0 :(得分:5)
现在与super.clone()有什么不同?
因为数组现在不同了。如果两个Stack
共享同一数组,则当一个添加或从堆栈中删除时,另一个size
中的Stack
字段不会更新,从而导致差异。
数组的对象本身不会被克隆。这是有意的,因为不需要克隆它们。预期两个Stack
-或实际上两个Collection
-可以包含对相同对象的引用。使用此代码,您将得到相同的行为:
Foo foo = new Foo()
Stack stackOne = new Stack();
Stack stackTwo = new Stack();
stackOne.push(foo);
stackTwo.push(foo);
这不是天生的问题,通常是理想的行为。
答案 1 :(得分:2)
您对clone
的工作原理绝对正确。支持数组中的对象不会被复制,但是支持数组中的对象将被复制。
这不是问题,因为调用者不希望元素被复制。对于像堆栈这样的集合类,“规范”是进行浅表复制。标准库中的一个示例是ArrayList
的副本构造函数。
还请注意,您可以通过克隆数组内部的对象来实现clone
(这意味着堆栈只能存储Clonable
个暴露{{ 1}})。这不会破坏clone
的合同。 contract非常宽松。
答案 2 :(得分:1)
修改原始图将破坏克隆中的不变式,反之亦然。
问题是,修改一个堆栈(将新元素推入其中或将其删除)会修改两个堆栈(如果它们要共享同一后备数组,但不一致),例如size
成员将在其中一个而不是另一个中更新。例如,如果不变量之一是数组中当前“堆栈顶部”之后的元素都不为空,则该不变量可能会被破坏。 (但是,对于这种特殊情况,我认为这不会直接导致异常)。
通过克隆,两个堆栈具有独立的数组(包含相同的元素),正如您推测的那样。然后将一个元素压入一个堆栈不会影响支持另一堆栈的数组的内容。
数组result.element的实际元素仍然指向原始Object引用。更改原始副本时,这仍然可能导致破坏克隆的不变性,反之亦然,不是吗?我想念什么吗?
不,不能。 (尝试提供一个如何发生这种情况的示例;您会发现自己很困惑)。堆栈类的不变量取决于数组的大小和内容(标识),而不取决于数组中对象的状态。
答案 3 :(得分:1)
您无能为力。 super.clone()
复制值成员,elements.clone()
创建一个新数组,因此新堆栈将具有与旧堆栈无关的独立存储,也就是堆栈结构本身的全部存储。
堆栈上的对象不一定是可克隆的,因此尝试克隆还是不逐个克隆是一个决定性的问题,这很可能是在文本中的某个地方解决的。 (附带说明:当您addAll()
,putAll()
或使用接受另一个容器的构造函数时,内置容器不会克隆对象)