如何正确覆盖克隆方法?
我需要在我的一个没有超类的对象中实现一个深层克隆。
处理由超类抛出的检查的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
:
- 被克隆的类没有实现Cloneable(假设实际的克隆最终会推迟到
Object
的克隆方法)。 如果您正在编写此方法的类实现Cloneable,则永远不会发生(因为任何子类都将适当地继承它)。 - 这个异常是由一个实现明确抛出的 – 当超类是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或其他噱头代码。