我见过这样编码的防御性副本
void someMethod(Date d) {
myDate = new Date( d.getTime() );
}
但这对我来说没有意义,Java中没有办法在该对象的内存中创建相同的副本吗?
我已经阅读了clone()
不会在所有情况下都有效,但我不明白为什么。
我见过这样编码的防御性副本
void someMethod(Date d) {
myDate = new Date( d.getTime() );
}
但这对我来说没有意义,Java中没有办法在该对象的内存中创建相同的副本吗?
我已经阅读了clone()
不会在所有情况下都有效,但我不明白为什么。
我可以尝试回答这个问题,但我只是在抄袭 Josh Bloch,所以这里有一个指向资源的链接:
复制构造函数与克隆
Bill Venners: 在您的书中,您建议使用复制构造函数,而不是实现
Cloneable
和编写clone
. 你能详细说明一下吗?Josh Bloch:如果你读过我书中关于克隆的内容,尤其是如果你读到字里行间,你就会知道我认为克隆被严重破坏了。有一些设计缺陷,其中最大的就是
Cloneable
接口没有clone
方法。这意味着它根本行不通:做某事Cloneable
并不能说明你可以用它做什么。相反,它说明了它可以在内部做什么。它说如果通过super.clone
重复调用它最终调用Object
'clone
方法,该方法将返回原始的字段副本。但是它没有说明你可以用实现
Cloneable
接口的对象做什么,这意味着你不能做多态clone
操作。如果我有一个 数组Cloneable
,您会认为我可以运行该数组并克隆每个元素以制作该数组的深层副本,但我不能。您不能将某些内容转换为Cloneable
并调用该clone
方法,因为Cloneable
没有公共clone
方法,也没有Object
. 如果您尝试强制转换Cloneable
并调用该clone
方法,编译器会说您正在尝试clone
在对象上调用受保护的方法。事情的真相是,除了复制能力之外,您没有通过实施
Cloneable
和提供公共方法来向客户提供任何能力。clone
如果您提供具有不同名称的复制操作并且您不实现Cloneable
. 这基本上就是您使用复制构造函数所做的事情。复制构造方法有几个优点,我在书中进行了讨论。一个很大的优点是可以使副本具有与原始版本不同的表示形式。例如,您可以将 a 复制LinkedList
到ArrayList
.
Object
的clone
方法非常棘手。它基于现场副本,并且是“语言外的”。它创建一个对象而不调用构造函数。不能保证它保留了构造函数建立的不变量。多年来,无论在 Sun 内部还是外部,都出现了许多错误,这是因为如果您只是super.clone
反复调用链直到您克隆了一个对象,您就会得到该对象的浅表副本。克隆通常与被克隆的对象共享状态。如果该状态是可变的,则您没有两个独立的对象。如果你修改一个,其他的也会改变。突然之间,你得到随机行为。
Cloneable
我不再使用的东西很少。clone
我经常在具体类上提供一个公共方法,因为人们期望它。我没有实现抽象类Cloneable
,也没有扩展它的接口,因为我不会将实现的负担Cloneable
放在所有扩展(或实现)抽象类(或接口)的类上。这是一个真正的负担,几乎没有什么好处。道格·李(Doug Lea)走得更远。他告诉我他不再使用
clone
除了复制数组。您应该使用clone
来复制数组,因为这通常是最快的方法。但是 Doug 的类型根本不再实现Cloneable
。他已经放弃了。我认为这并非不合理。被打破是一种耻辱
Cloneable
,但它发生了。最初的 Java API 在紧迫的期限内很快完成,以满足即将关闭的市场窗口。最初的 Java 团队做得非常出色,但并非所有的 API 都是完美的。Cloneable
是一个弱点,我认为人们应该意识到它的局限性。
clone()
仅在某些类中有意义地实现,因为在克隆过程中有很多 JVM 或编译器不会为您做出的决定(例如,浅拷贝与深拷贝)。有些人还会争辩说 Java 中的整个概念都被破坏了,因此很少使用。
然而,最终结果是许多类不能被克隆。在这些情况下,用户通过基于另一个对象生成和初始化一个对象来克隆它们。
但是,Date
类 implements Cloneable
,因此与仅使用“复制构造函数”相比,在这种特定情况下并没有真正的好处。
最好使用克隆的一种情况是使用类层次结构。想象一下,您将数学表达式表示为表达式子类型的树,您在其中引用最外层的表达式。
假设在这种情况下它是一个加号。你会在引用表达式时调用 clone,但你真正得到的是一个 Plus 的新实例。使用构造函数你不能真正做到这一点,因为你的表达式可能是一个接口或一个抽象类。
没有简单的方法可以制作始终有效的相同副本。
克隆应该这样做,但并非所有类都实现它,因此或多或少被破坏了。
另一种方法是序列化/反序列化,但并非所有类都支持序列化。
从移动代码安全的角度来看,clone
通常可以被覆盖。
@Override public Date clone() {
return this; // Ha!
}
即使您不关心安全性,您也可以想象一个“聪明”的程序员会在您的代码中导致错误报告。
clone
特别是返回完全相同的运行时类型的期望也会导致实现问题。