How to properly override clone method?

我需要在一个没有超类的对象中实现一个深度克隆。

处理超类(即 Object)抛出的检查后的 CloneNotSupportedException的最佳方法是什么?

一位同事建议我用以下方法来处理:

@Override
public MyObject clone()
{
MyObject foo;
try
{
foo = (MyObject) super.clone();
}
catch (CloneNotSupportedException e)
{
throw new Error();
}


// Deep clone member fields here


return foo;
}

对我来说,这似乎是一个很好的解决方案,但是我想把它扔给 StackOverflow 社区,看看是否还有其他我可以包括在内的见解。谢谢!

124056 次浏览

你一定要使用 clone吗? 大多数人都认为 Java 的 clone已经坏了。

乔希 · 布洛克谈设计复制建构子与克隆

如果你读过我书中关于克隆的内容,尤其是字里行间的意思,你就会知道我认为 clone已经被严重破坏了。很遗憾 Cloneable坏了,但它确实发生了。

你可以在他的书 有效的 Java 第2版,第11项: 明智地覆盖 clone中读到更多关于这个主题的讨论。他建议使用复制建构子或复印厂。

他接着写了一页又一页的内容,说明如果你觉得必须实现 clone,你应该如何实现。但他以这句话结束了演讲:

Is all this complexities really necessary? Rarely. If you extend a class that implements Cloneable, you have little choice but to implement a well-behaved clone method. Otherwise, 你最好提供对象复制的替代方法,或者干脆不提供这种能力.

重点是他的,不是我的。


既然您已经明确表示除了实现 clone之外别无选择,那么在这种情况下您可以这样做: 确保 MyObject extends java.lang.Object implements java.lang.Cloneable。如果是这种情况,那么您可以保证您将 从来没有捕获一个 CloneNotSupportedException。抛出 AssertionError作为一些建议似乎是合理的,但您也可以添加一个注释,解释为什么捕获块将永远不会进入 在这个特殊的情况下


或者,正如其他人所建议的,您也许可以实现 clone而不调用 super.clone

有时候实现一个复制建构子会更简单:

public MyObject (MyObject toClone) {
}

它为您节省了处理 CloneNotSupportedException的麻烦,可以处理 final字段,并且您不必担心返回的类型。

Use 序列化 to make deep copies. This is not the quickest solution but it does not depend on the type.

有两种情况会抛出 CloneNotSupportedException:

  1. 被克隆的类没有实现 Cloneable(假设实际的克隆最终遵从 Object的克隆方法)。如果在 Cloneable中编写此方法的类实现了 Cloneable,那么这种情况将永远不会发生(因为任何子类都将适当地继承它)。
  2. 异常是由实现显式抛出的——当超类为 Cloneable时,这是防止子类可克隆性的推荐方法。

The latter case cannot occur in your class (as you're directly calling the superclass' method in the try block, even if invoked from a subclass calling super.clone()) and the former should not since your class clearly should implement Cloneable.

基本上,您应该肯定地记录错误,但是在这个特定的实例中,只有在您搞砸了类的定义时才会发生错误。因此,就像对待 NullPointerException(或类似的)的检查版本一样对待它——如果你的代码是功能性的,它将永远不会被抛出。


在其他情况下,你需要为这种可能性做好准备——不能保证给定的对象 是可克隆的,所以在捕获异常时,你应该根据这种情况采取适当的措施(继续使用现有的对象,采取另一种克隆策略,例如序列化-反序列化,如果你的方法需要可克隆的参数,抛出一个 IllegalParameterException,等等)。

编辑 : 虽然总的来说我应该指出是的,但是 clone()确实很难正确实现,而且调用者很难知道返回值是否是他们想要的,当你考虑深克隆和浅克隆时更是如此。通常最好是完全避免这种情况,使用另一种机制。

代码的工作方式非常接近于“规范”的编写方式。不过我会在接球的时候扔一个 AssertionError。这意味着这条线永远不能到达。

catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}

您可以像下面这样实现受保护的复制构造函数:

/* This is a protected copy constructor for exclusive use by .clone() */
protected MyObject(MyObject that) {
this.myFirstMember = that.getMyFirstMember(); //To clone primitive data
this.mySecondMember = that.getMySecondMember().clone(); //To clone complex objects
// etc
}


public MyObject clone() {
return new MyObject(this);
}
public class MyObject implements Cloneable, Serializable{


@Override
@SuppressWarnings(value = "unchecked")
protected MyObject clone(){
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
ByteArrayOutputStream bOs = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bOs);
oos.writeObject(this);
ois = new ObjectInputStream(new ByteArrayInputStream(bOs.toByteArray()));
return  (MyObject)ois.readObject();


} catch (Exception e) {
//Some seriouse error :< //
return null;
}finally {
if (oos != null)
try {
oos.close();
} catch (IOException e) {


}
if (ois != null)
try {
ois.close();
} catch (IOException e) {


}
}
}
}

尽管这里的大多数答案都是有效的,但我需要告诉大家,您的解决方案也是实际的 JavaAPI 开发人员如何实现的。(不是 Josh Bloch 就是 Neal Gafter)

下面是 openJDK,ArrayList 类的一个摘录:

public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}

As you have noticed and others mentioned, CloneNotSupportedException has almost no chance to be thrown if you declared that you implement the Cloneable interface.

另外,如果在重写的方法中不执行任何新操作,则不需要重写该方法。只有在需要对对象执行额外操作或需要将其公开时,才需要重写它。

Ultimately, it is still best to avoid it and do it using some other way.

Just because java's implementation of Cloneable is broken it doesn't mean you can't create one of your own.

如果 OP 的真正目的是创建一个深度克隆,我认为有可能创建这样一个接口:

public interface Cloneable<T> {
public T getClone();
}

然后使用前面提到的原型构造函数来实现它:

public class AClass implements Cloneable<AClass> {
private int value;
public AClass(int value) {
this.vaue = value;
}


protected AClass(AClass p) {
this(p.getValue());
}


public int getValue() {
return value;
}


public AClass getClone() {
return new AClass(this);
}
}

以及另一个带有 AClass 对象字段的类:

public class BClass implements Cloneable<BClass> {
private int value;
private AClass a;


public BClass(int value, AClass a) {
this.value = value;
this.a = a;
}


protected BClass(BClass p) {
this(p.getValue(), p.getA().getClone());
}


public int getValue() {
return value;
}


public AClass getA() {
return a;
}


public BClass getClone() {
return new BClass(this);
}
}

通过这种方式,您可以轻松地深度克隆类 BClass 的对象,而不需要@SuppressWarnings 或其他花哨的代码。