题
我需要在我的一个没有超类的对象中实现深度克隆。
处理检查的最佳方式是什么 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
被打破,但它发生。
您可以阅读在他的书中的话题更多的讨论的有效的Java第二版,第11项:覆盖clone
明智的。他建议,而不是使用拷贝构造函数或复制工厂。
他接着页面写入网页上如何,如果你觉得你一定要,你应该实现clone
。但是他这个封闭:
时的这一切复杂真的有必要吗?很少。如果要扩展一个类实现
Cloneable
,你别无选择,只能实现一个乖巧clone
方法。否则,您是关闭提供对象拷贝的替代手段,或者干脆不提供能力强>更好。
的重点是他的,不是我的。
既然你说得很清楚,你别无选择,只能实行clone
,这里就是你可以在这种情况下怎么办:确保MyObject extends java.lang.Object implements java.lang.Cloneable
。如果是这样的话,那么你就可以保证你将绝不会赶上CloneNotSupportedException
。投掷AssertionError
一些建议似乎是合理的,但你也可以添加注释,解释了为什么catch块将永远不会进入在这种特殊情况下
另外,正如其他人还建议,可以或许实现clone
,而无需调用super.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
(或类似) - 如果您的代码有效,则永远不会抛出该异常。
在其他情况下,您需要为这种可能性做好准备 - 不能保证给定的对象 是 可克隆,因此当捕获异常时,您应该根据这种情况采取适当的操作(继续使用现有对象,采取替代克隆策略,例如序列化-反序列化,抛出一个 IllegalParameterException
如果你的方法需要通过cloneable等参数。ETC。)。
编辑: :虽然总的来说我应该指出的是, 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开发人员是如何做到这一点。 (任乔希布洛赫或尼尔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);
}
}
正如你已经注意到了和其他人提到,CloneNotSupportedException
有,如果你宣布你实现Cloneable
界面几乎没有机会抛出。
同时,有没有必要为你覆盖的方法,如果你没有做任何事情在重写方法新。你只需要在你需要做的对象额外的操作来覆盖它,或者你需要把它公开。
最终,它仍是最好的,以避免它和做它使用一些其他方式。
仅仅因为Cloneable的Java的实现是坏,这并不意味着你不能创建你自己的。
如果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);
}
}
在这种方式可以easely深克隆类BClass的目的,而不需要@SuppressWarnings或其他花哨代码。