如何正确覆盖克隆方法?

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

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

一位同事建议我按照以下方式处理:

 @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社区,看看是否有任何其他的见解我可以包括。 谢谢!

你绝对不得不使用clone ? 大多数人都认为Java的clone已经被破坏了。

乔希布洛赫在设计 – 复制构造与克隆

如果你已经阅读了我书中关于克隆的内容,特别是如果你在各行之间阅读,你会知道我认为clone已经被深深地打破了。 Cloneable被破解是一件Cloneable事情,但事实恰恰如此。

您可以在他的书“ 有效Java第2版”第11项:明智地重写clone阅读关于该主题的更多讨论。 他建议使用复制构造函数或复制工厂。

他接着写了几页,如果你觉得你必须,你应该实现clone 。 但他用这样的话来说:

所有这些复杂性真的有必要吗? 很少。 如果你扩展一个实现了Cloneable的类,你别无选择,只能实现一个行为良好的clone方法。 否则, 你最好提供替代的对象复制手段,或者根本不提供这种能力

重点是他的,而不是我的。


既然你明确表示除了实现clone别无选择,下面是在这种情况下可以做的事情:确保MyObject extends java.lang.Object implements java.lang.Cloneable 。 如果是这样的话,那么你可以保证你永远不会遇到CloneNotSupportedException 。 抛出AssertionError正如一些人所建议的似乎是合理的,但你也可以添加一个注释,解释为什么catch块将永远不会进入这个特定的情况


或者,正如其他人也建议的那样,您可以在不调用super.clone情况下实现clone

有时实现复制构造函数更简单:

 public MyObject (MyObject toClone) { } 

它为您节省了处理CloneNotSupportedException的麻烦,与final字段一起工作,您不必担心要返回的类型。

你的代码的工作方式非常接近写“规范”的方式。 但是,我会抛出一个AssertionError 。 它表示该线路不应该达到。

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

有两种情况会引发CloneNotSupportedException

  1. 被克隆的类没有实现Cloneable(假设实际的克隆最终会推迟到Object的克隆方法)。 如果您正在编写此方法的类实现Cloneable,则永远不会发生(因为任何子类都将适当地继承它)。
  2. 这个异常是由一个实现明确抛出的 – 当超类是Cloneable时,这是推荐在子类中阻止可克隆的方法。

后一种情况不会发生在你的类中(因为你直接在try块中调用超类的方法,即使从调用super.clone()的子类调用),前者不应该,因为你的类显然应该实现Cloneable。

基本上,你应该记录这个错误,但是在这个特殊的情况下,只有在你搞乱了类的定义的时候才会发生。 因此把它看作是一个NullPointerException(或类似的)的检查版本 – 如果你的代码是可用的,它永远不会被抛出。


在其他情况下,您需要为这种可能性做好准备 – 不能保证给定的对象是可复制的,因此捕捉异常时应根据这种情况采取适当的措施(继续使用现有对象,采取其他克隆策略例如serialise-deserialise,如果你的方法需要可复制的参数等,则抛出一个IllegalParameterException异常)。

编辑 :虽然总体上我应该指出,是的, clone()真的很难正确实现,很难让调用者知道返回值是否是他们想要的,当你考虑深层与浅层克隆时,加倍是这样。 通常最好避免整个事情,并使用另一种机制。

使用序列化来制作深层复制。 这不是最快的解决方案,但不依赖于类型。

你可以像这样实现受保护的拷贝构造函数:

 /* 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) { } } } } 

尽管这里的大多数答案都是有效的,但我需要告诉您的解决方案也是实际的Java API开发人员如何做到的。 (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); } } 

正如你已经注意到的和其他人提到的,如果你声明你实现了Cloneable接口, CloneNotSupportedException几乎没有机会被抛出。

而且,如果您在重写的方法中不执行任何新操作,则不需要重写该方法。 您只需要在对象上执行额外操作时将其覆盖,或者需要将其公开。

最终,最好避免它,并采取其他方式。

仅仅因为java的Cloneable的实现被破坏了,并不意味着你不能创建自己的一个。

如果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或其他噱头代码。

Interesting Posts