我一直在寻找一些讲解Java的教程Cloneable
,但没有获得任何良好的链接,无论如何,Stack Overflow变得越来越明显。
我想知道以下内容:
Cloneable
意味着我们可以通过实现Cloneable
接口来获得对象的克隆或副本。这样做的优点和缺点是什么?- 如果对象是复合对象,则如何进行递归克隆?
我一直在寻找一些讲解Java的教程Cloneable
,但没有获得任何良好的链接,无论如何,Stack Overflow变得越来越明显。
我想知道以下内容:
Cloneable
意味着我们可以通过实现Cloneable
接口来获得对象的克隆或副本。这样做的优点和缺点是什么? Answers:
您应该了解的第一件事Cloneable
-不要使用它。
Cloneable
正确地进行克隆非常困难,而且付出的努力是不值得的。
而不是使用其他一些选项,例如apache-commons SerializationUtils
(deep-clone)或BeanUtils
(shallow-clone),或仅使用复制构造函数。
有关 Josh Bloch关于with克隆的观点,请参见此处Cloneable
,这解释了该方法的许多缺点。(Joshua Bloch是Sun的雇员,并领导了许多Java功能的开发。)
static
在接口中使用方法,因此只需提供一个static WhatEverTheInterface copy(WhatEverTheInterface initial)
?但是我不知道这会给您带来什么,因为您在克隆时从对象复制字段,但是接口仅定义方法。关心解释?
不幸的是,Cloneable本身只是一个标记接口,即:它没有定义clone()方法。
它的作用是更改受保护的Object.clone()方法的行为,该方法将为未实现Cloneable的类抛出CloneNotSupportedException,并为实现该类的成员执行成员方式的浅表复制。
即使这是您要寻找的行为,您仍然需要实现自己的clone()方法以使其公开。
在实现自己的clone()时,其想法是从用super.clone()创建的对象开始,该对象必须是正确的类,然后再进行其他任何字段填充,以防浅表副本不是你要。从clone()调用构造函数会带来问题,因为如果子类想要添加自己的其他可克隆逻辑,则这将破坏继承。如果要调用super.clone(),则在这种情况下将获得错误类的对象。
但是,此方法绕过了可能在构造函数中定义的任何逻辑,这可能会带来问题。
另一个问题是,任何忘记覆盖clone()的子类将自动继承默认的浅表副本,在可变状态下(这将在源和副本之间共享),这可能不是您想要的。
由于这些原因,大多数开发人员不使用Cloneable,而只是实现一个复制构造函数。
有关Cloneable的更多信息和潜在陷阱,我强烈推荐Joshua Bloch撰写的《 Effective Java》一书。
因此,请谨慎使用Cloneable。与为正确完成所有事情而需要付出的努力相比,它没有给您带来足够的好处。
克隆是基本的编程范例。Java可能在许多方面都没有很好地实现它,这一事实丝毫不会减少克隆的需要。而且,很容易实现将起作用的克隆,但是无论您希望它起作用如何,浅层,深层,混合层等等。您甚至可以为函数使用名称克隆,并且如果愿意,可以不实现Cloneable。
假设我有类A,B和C,其中B和C都是从A派生的。如果我有一个类型为A的对象的列表,如下所示:
ArrayList<A> list1;
现在,该列表可以包含A,B或C类型的对象。您不知道这些对象是什么类型。因此,您不能像这样复制列表:
ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
list2.add(new A(a));
}
如果对象实际上是B或C类型,则不会获得正确的副本。而且,如果A是抽象的怎么办?现在,有人建议这样做:
ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
if(a instanceof A) {
list2.add(new A(a));
} else if(a instanceof B) {
list2.add(new B(a));
} else if(a instanceof C) {
list2.add(new C(a));
}
}
这是一个非常非常糟糕的主意。如果添加新的派生类型怎么办?如果B或C在另一个程序包中,而您在此类中没有访问权限,该怎么办?
您想做的是这样的:
ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
list2.add(a.clone());
}
很多人已经指出了为什么克隆的基本Java实现存在问题。但是,通过这种方式很容易克服:
在A类中:
public A clone() {
return new A(this);
}
在B类中:
@Override
public B clone() {
return new B(this);
}
在C类中:
@Override
public C clone() {
return new C(this):
}
我没有实现Cloneable,只是使用了相同的函数名。如果您不喜欢该名称,请改用其他名称。
可克隆的缺点是什么?
如果要复制的对象具有组成,则克隆是非常危险的。在这种情况下,您需要考虑以下可能的副作用,因为克隆会创建浅拷贝:
假设您有一个对象来处理与数据库相关的操作。说,该对象具有Connection
对象作为属性之一。
因此,当有人创建的克隆时originalObject
,假设创建的对象是cloneObject
。在这里,originalObject
和cloneObject
拥有相同的Connection
对象参考。
假设说originalObject
关闭了该Connection
对象,那么cloneObject
由于该connection
对象在它们之间共享,并且实际上已被关闭,因此will将不起作用originalObject
。
如果假设您要克隆具有IOStream作为属性的对象,则可能会发生类似的问题。
如果对象是复合对象,则如何进行递归克隆?
克隆执行浅拷贝。这意味着原始对象和克隆对象的数据将指向相同的引用/内存。与深度复制相反,将原始对象的内存中的数据复制到克隆对象的内存中。
Cloneable
不执行复制,而是执行Object.clone
。正是“将原始对象的内存中的数据复制到克隆对象的内存中” Object.clone
。您需要谈论引用对象的内存来描述深度复制。