Java防御副本

时间:2010-02-10 04:38:11

标签: java theory defensive-programming

我看过像这样编码的防御性副本

void someMethod(Date d) {
    myDate = new Date( d.getTime() );
}

但这对我来说没有意义,在Java中是否有办法在该对象的内存中创建相同的副本?

我看过clone()在所有情况下都不起作用,但我不明白为什么。

4 个答案:

答案 0 :(得分:7)

我可以尝试回答这个问题,但我只是抄袭Josh Bloch,所以这里是资源的链接:

  

Copy Constructor versus Cloning

     

Bill Venners: 在您的图书中,您建议使用复制构造函数,而不是实现Cloneable并编写clone。你能详细说明吗?

     

Josh Bloch:如果您已经阅读了我的书中有关克隆的项目,特别是如果您在这些内容之间进行了阅读,您将会知道我认为克隆已经深受打击。存在一些设计缺陷,其中最大的缺点是Cloneable接口没有clone方法。这意味着它根本不起作用:制作一些Cloneable并没有说明你可以用它做什么。相反,它说明了内部可以做些什么。它说如果反复调用super.clone,最终会调用Object的{​​{1}}方法,此方法将返回原始字段的字段副本。

     

但它没有说明你可以用一个实现clone接口的对象做什么,这意味着你不能进行多态Cloneable操作。如果我有一个clone数组,你会认为我可以运行该数组并克隆每个元素以制作数组的深层副本,但我不能。您无法向Cloneable投射内容并调用Cloneable方法,因为clone没有公开Cloneable方法,clone也没有。如果您尝试强制转换为Object并调用Cloneable方法,编译器会说您正在尝试在对象上调用受保护的clone方法。

     

事情的真相是,您不会通过实施clone并提供除复制功能之外的公共Cloneable方法向您的客户提供任何功能。如果您提供具有不同名称的复制操作并且未实现clone,则这并不比您获得的更好。这基本上就是你用复制构造函数做的事情。复制构造方法有几个优点,我在本书中讨论。一个很大的优点是可以使副本具有与原始副本不同的表示。例如,您可以将Cloneable复制到LinkedList

     

ArrayList的{​​{1}}方法非常棘手。它基于现场副本,而且是“超语言”。它创建一个对象而不调用构造函数。无法保证它保留构造函数建立的不变量。多年来,在Sun内外都存在许多错误,这源于这样一个事实,即如果你只是反复调用Object直到你克隆了一个对象,你就会得到一个浅层的对象副本。克隆通常与正在克隆的对象共享状态。如果该状态是可变的,则您没有两个独立的对象。如果您修改一个,另一个也会更改。突然之间,你会得到随机行为。

     

我使用clone的东西很少。我经常在具体课程上提供公共super.clone方法,因为人们期望它。我没有抽象类实现Cloneable,也没有接口扩展它,因为我不会在扩展(或实现)抽象类的所有类上放置实现clone的负担(或界面)。这是一个真正的负担,几乎没有什么好处。

     Doug Lea走得更远。他告诉我他除了复制数组之外不再使用Cloneable了。您应该使用Cloneable来复制数组,因为这通常是最快的方法。但Doug的类型根本不再实现clone。他放弃了。而且我认为这并非不合理。

     

clone被打破是一种耻辱,但它确实发生了。最初的Java API在紧迫的期限内完成,以满足收盘市场的需求。最初的Java团队做了不可思议的工作,但并非所有的API都是完美的。 Cloneable是一个弱点,我认为人们应该意识到它的局限性。

答案 1 :(得分:3)

clone()仅在某些类中有意义地实现,因为克隆中涉及的许多决策都是JVM或编译器没有为您做出的(例如,浅层复制和深层复制)。有些人还认为Java中的整个概念已被破坏,因此很少使用。

然而,最终结果是无法克隆许多类。在这些情况下,用户通过基于另一个生成和初始化一个对象来克隆它们。

但是,Date类实现了Cloneable,因此与仅使用“复制构造函数”相比,在这种特定情况下没有任何好处。

克隆是首选的一种情况是使用类层次结构时。想象一下,您将数学表达式表示为表达式子类型的树,您可以在其中引用 最外层 表达式。

让我们说在这种情况下它是一个加号。你可以在对Expression的引用上调用clone,但你真正得到的是一个新的Plus实例。使用构造函数,您无法真正做到这一点,因为您的Expression可以是接口或抽象类。

答案 2 :(得分:1)

没有简单的方法可以制作始终有效的相同副本。

Clone应该这样做,但它并没有被所有类实现,因此或多或少地被破坏了。

另一种方法是序列化/反序列化,但所有类都不支持序列化。

答案 3 :(得分:0)

从移动设备安全的角度来看,clone通常可以被覆盖。

@Override public Date clone() {
    return this; // Ha!
}

即使您不关心安全性,您也可以想象程序员“聪明”,导致代码中出现错误报告。

期望特别是clone将返回完全相同的运行时类型也会导致实现问题。