Java 使用可怕的克隆习语是克隆未知(子)类型对象的唯一方法吗?
我有一个类(“Manager”),它管理一个对象集合,所有对象都植根于一个公共超类(“Managed”)。manager类有时需要复制选定的托管对象,但无法知道它是托管对象的哪个子类。在我看来,最好的(如果不是唯一的?)方法是使用Cloneable。然后,对于需要复制的任何托管对象,我调用managedObject.clone()。当然,它必须得到适当的实施。我已经阅读了许多关于“只使用复制构造函数”或为所有子类实现myManagedSubClass.copy()方法的告诫。我不知道如何使用“真实”复制构造函数,因为我需要知道类型:Java 使用可怕的克隆习语是克隆未知(子)类型对象的唯一方法吗?,java,cloneable,Java,Cloneable,我有一个类(“Manager”),它管理一个对象集合,所有对象都植根于一个公共超类(“Managed”)。manager类有时需要复制选定的托管对象,但无法知道它是托管对象的哪个子类。在我看来,最好的(如果不是唯一的?)方法是使用Cloneable。然后,对于需要复制的任何托管对象,我调用managedObject.clone()。当然,它必须得到适当的实施。我已经阅读了许多关于“只使用复制构造函数”或为所有子类实现myManagedSubClass.copy()方法的告诫。我不知道如何使用“真
ManagedSubclass copiedObject = new ManagedSubclass(existingManagedSubclassObject);
如果我实现copy()方法,我想会是这样的:
class Managed {
public Managed copy() {
Managed newObject = new Managed(Managed other);
// fixup mutable fields in newObject
}
}
但是在我的使用中,我必须将返回值强制转换为期望的类型。如果我忘了在所有托管子类上实现copy(),那么我最终会将一个超类转换为一个子类类型。我无法在托管上设置受复制保护的可见性,因为该类对于直接复制是有效的。即使不是这样,我也必须在每个可以复制的子类上实现复制,使用所有的机制来处理可变字段的深度复制,或者建立我自己的协议,使用某个通用名称的受保护方法来处理该级别超类引入的所有可变字段
看来,尽管人们普遍对Cloneable感到愤怒和憎恨,但这是做我想做的事情的最佳方式。我遗漏了什么吗?克隆的力量来自于它与继承一起工作的运行时动态行为,而复制构造函数不适合这种行为 使对象可克隆的标准方法是:
clone()
中,调用super.clone()
托管对象=(托管)super.clone()李>
Managed
被继承,并且子类正确地实现了克隆,那么克隆它将返回正确的类型。e、 g
Managed m = new SubTypeOfManaged();
m.clone(); // returns a cloned SubTypeOfManaged
在正确的时刻使用正确的工具 如果您需要
可克隆
,请使用它。但要做到这一点,就要知道它拥有的所有流量
clone()
名声不好,因为它太复杂,做什么都不好。除非您有final字段或零参数构造函数调用另一个构造函数,否则您应该可以使用它,只要您按照建议实现它。我更喜欢使用复制构造函数来复制可变对象。编写构造函数时,您必须调用super(…)
,在这里您可以使用super类的复制构造函数。这种调用超类的构造函数然后分配当前类的字段的方法类似于编写clone
方法(调用super.clone()
然后在必要时重新分配字段)。与克隆相比,它的一个优点是,您永远不必使用无用的try{…}catch(CloneNotSupportedException e){}
构造。与使用clone
相比,复制构造函数的另一个优点是,您可以创建可变字段final
,而clone
要求您在调用super.clone()
后使用原始字段的副本重新分配字段
编写copy
方法时不能使用继承,因为super.copy()
返回一个超类的实例。但是,如果您喜欢使用方法而不是构造函数,那么除了复制构造函数之外,还可以提供copy
方法
这是一个例子
interface Copyable {
Copyable copy();
}
class ImplA implements Copyable {
private String field;
public ImplA(ImplA implA) {
this.field = implA.field;
}
@Override
public ImplA copy() {
return new ImplA(this);
}
// other constructors and methods that mutate state.
}
class ImplB extends ImplA {
private int value;
private final List<String> list; // This field could not be final if we used clone.
public ImplB(ImplB implB) {
super(implB); // Here we invoke the copy constructor of the super class.
this.value = implB.value;
this.list = new ArrayList<>(implB.list);
}
@Override
public final ImplB copy() {
return new ImplB(this);
}
// other constructors and methods that mutate state.
}
接口可复制{
可复制副本();
}
类ImplA实现了可复制{
私有字符串字段;
公共ImplA(ImplA ImplA){
this.field=implA.field;
}
@凌驾
公开副本(){
返回新的ImplA(本);
}
//改变状态的其他构造函数和方法。
}
类ImplB扩展了ImplA{
私有int值;
private final List;//如果使用克隆,则此字段不能为final。
公共ImplB(ImplB ImplB){
super(implB);//这里我们调用super类的复制构造函数。
this.value=implB.value;
this.list=新的ArrayList(implB.list);
}
@凌驾
公开最终执行副本(){
返回新的ImplB(本);
}
//改变状态的其他构造函数和方法。
}
无论您使用的是copy()
还是clone()
它都必须是为您使用的接口定义并公开的方法。之后,您将取决于此方法的实现情况。也许您可以定义自己的接口,Copyable
,并要求托管对象使用合适的copy()
方法来实现它。+1用于指出最后的字段限制。然而,我仍然认为使用复制构造函数的“首选”方法对我来说是行不通的。我需要能够复制泛型类型的对象。ForListOfManaged{void doSomething(T managedSubclass)…}我如何使用复制构造函数在该方法体中复制doSomething()的参数?@ags你是对的,你不能在泛型T
上调用复制构造函数,但是如果Managed
有copy
方法,你可以执行(T)T.copy()
。我只是觉得使用copy构造函数(如上所述)编写copy
比使用clone.+1更好,因为它指出了final字段和零参数构造函数的局限性(尽管我不得不考虑一下)。