如何正确覆盖克隆方法?
Posted
技术标签:
【中文标题】如何正确覆盖克隆方法?【英文标题】:How to properly override clone method? 【发布时间】:2011-01-20 13:41:14 【问题描述】:我需要在我的一个没有超类的对象中实现深度克隆。
处理超类(即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;
这对我来说似乎是一个很好的解决方案,但我想把它扔给 *** 社区,看看是否还有其他可以包含的见解。谢谢!
【问题讨论】:
如果你知道父类实现了Cloneable
,那么抛出一个AssertionError
而不仅仅是一个普通的Error
更有表现力。
编辑:我宁愿不使用 clone(),但项目已经基于它,此时不值得重构所有引用。
最好现在咬紧牙关而不是以后(好吧,除非您即将投入生产)。
我们距离完成还有一个半月的时间。我们无法证明时间的合理性。
我认为这个解决方案是完美的。并且不要对使用克隆感到难过。如果您有一个用作传输对象的类,并且它只包含原始数据和不可变数据,如 String 和 Enums,那么由于教条的原因,除了 clone 之外的所有内容都将完全浪费时间。请记住克隆做什么和不做什么! (没有深度克隆)
【参考方案1】:
你一定要使用clone
吗?大多数人都同意 Java 的 clone
已损坏。
Josh Bloch on Design - Copy Constructor versus Cloning
如果你读过我书中关于克隆的内容,尤其是如果你读到字里行间,你就会知道我认为
clone
被深深地破坏了。 [...] 很遗憾Cloneable
坏了,但它确实发生了。
您可以在他的书Effective Java 2nd Edition, Item 11: Override clone
judiciously 中阅读更多关于该主题的讨论。他建议改用复制构造函数或复制工厂。
他接着写了几页关于如果你觉得必须的话,你应该如何实现clone
。但他以这个结尾:
所有这些复杂性真的有必要吗?很少。如果你扩展一个实现
Cloneable
的类,你别无选择,只能实现一个行为良好的clone
方法。否则,最好提供对象复制的替代方法,或者干脆不提供该功能。
重点是他的,不是我的。
既然您明确表示您别无选择,只能实施 clone
,在这种情况下您可以执行以下操作:确保 MyObject extends java.lang.Object implements java.lang.Cloneable
。如果是这种情况,那么您可以保证您将永远捕获CloneNotSupportedException
。正如一些人所建议的那样,抛出 AssertionError
似乎是合理的,但您也可以添加注释来解释为什么永远不会输入 catch 块在这种特殊情况下。
或者,正如其他人也建议的那样,您也许可以在不调用 super.clone
的情况下实现 clone
。
【讨论】:
不幸的是,该项目已经使用克隆方法编写,否则我绝对不会使用它。我完全同意你的观点,Java 的克隆实现是 fakakta。 如果一个类及其所有超类在其克隆方法中调用super.clone()
,则子类通常只需在添加内容需要克隆的新字段时覆盖clone()
。如果任何超类使用new
而不是super.clone()
,则所有子类都必须覆盖clone()
,无论它们是否添加任何新字段。【参考方案2】:
有时实现复制构造函数更简单:
public MyObject (MyObject toClone)
它为您省去了处理CloneNotSupportedException
的麻烦,与final
字段一起使用,您不必担心返回的类型。
【讨论】:
【参考方案3】:您的代码的工作方式非常接近于“规范”的编写方式。不过,我会在捕获中抛出一个AssertionError
。它表示永远不应该到达那条线。
catch (CloneNotSupportedException e)
throw new AssertionError(e);
【讨论】:
查看@polygenelubricants 的回答,了解为什么这可能是个坏主意。 @KarlRichter 我已经阅读了他们的答案。它并没有说这是一个坏主意,除了Cloneable
通常是一个坏主意,正如有效 Java 中所解释的那样。 OP 已经表示他们必须使用Cloneable
。所以我不知道我的答案还有什么可以改进的,除了可能直接删除它。【参考方案4】:
有两种情况会抛出CloneNotSupportedException
:
-
被克隆的类没有实现
Cloneable
(假设实际的克隆最终还是遵循Object
的克隆方法)。如果您在其中编写此方法的类实现了Cloneable
,则永远不会发生这种情况(因为任何子类都会适当地继承它)。
异常是由实现显式抛出的 - 当超类为 Cloneable
时,这是防止子类中可克隆性的推荐方法。
后一种情况不能发生在您的类中(因为您直接在 try
块中调用超类的方法,即使是从调用 super.clone()
的子类中调用),前一种情况不应发生,因为您的类显然应该实现Cloneable
。
基本上,您应该肯定地记录错误,但在这种特殊情况下,只有在您弄乱了类的定义时才会发生这种情况。因此将其视为NullPointerException
(或类似)的检查版本 - 如果您的代码正常运行,它将永远不会被抛出。
在其他情况下,您需要为这种可能性做好准备 - 无法保证给定对象是可克隆的,因此在捕获异常时,您应该根据这种情况采取适当的措施(继续对于现有对象,采用另一种克隆策略,例如序列化-反序列化,如果您的方法需要可克隆的参数等,则抛出IllegalParameterException
。
编辑:虽然总的来说我应该指出,是的,clone()
确实很难正确实现,而且调用者很难知道返回值是否是他们想要的,当你考虑深克隆与浅克隆。完全避免整个事情并使用另一种机制通常会更好。
【讨论】:
如果一个对象公开了一个公共克隆方法,任何不支持它的派生对象都会违反里氏替换原则。如果克隆方法受到保护,我认为最好用其他方法而不是返回正确类型的方法来隐藏它,以防止子类甚至尝试调用super.clone()
。【参考方案5】:
使用serialization 进行深层复制。这不是最快的解决方案,但它不依赖于类型。
【讨论】:
考虑到我已经在使用 GSON,这是一个很棒且显而易见的解决方案。【参考方案6】:您可以像这样实现受保护的复制构造函数:
/* 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);
【讨论】:
这在大多数情况下都不起作用。你不能对getMySecondMember()
返回的对象调用clone()
,除非它有public clone
方法。
显然,您需要在希望能够克隆的每个对象上实现此模式,或者找到另一种方法来深度克隆每个成员。
是的,这是必要的要求。【参考方案7】:
尽管这里的大多数答案都是有效的,但我需要告诉您,您的解决方案也是实际的 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
几乎没有机会被抛出。
另外,如果您在被覆盖的方法中没有做任何新的事情,则不需要覆盖该方法。仅当需要对对象进行额外操作或需要将其公开时才需要覆盖它。
最终,最好还是避免它并使用其他方式。
【讨论】:
【参考方案8】: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)
【讨论】:
所以你刚刚将它写入文件系统并读回了对象。好的,这是处理克隆的最佳方法吗? SO社区的任何人都可以评论这种方法吗?我想这不必要地将克隆和序列化 - 两个完全不同的概念联系起来。我会等着看其他人对此有什么看法。 它没有进入文件系统,它在主内存中(ByteArrayOutputStream)。对于深度嵌套的对象,它的解决方案效果很好。特别是如果您不经常需要它,例如在单元测试中。其中性能不是主要目标。【参考方案9】:仅仅因为 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 或其他花哨的代码。
【讨论】:
以上是关于如何正确覆盖克隆方法?的主要内容,如果未能解决你的问题,请参考以下文章
实现 Cloneable 接口如何允许克隆对象,因为它是一个标记接口并且没有任何方法? [复制]