乐趣区

Java 作者谈克隆方法的实现

今天在用 sonar 审核代码, 偶然看到下面的提示:
关于这个的提示大意是:
“克隆”不应该被覆盖, 属坏味道, 阻断型错误约书亚•布洛赫表示,许多人在 Java 中对 clone 方法 和 Cloneable 接口存在误解,很大程度上是因为重写 clone 方法的规则很棘手, 且出错难以纠正。Object 的 clone 方法非常棘手。它基于属性复制,而且是“超语言”。它创建一个对象而不调用构造函数。无法保证它保留构造函数创建的不变量。多年来,在 Sun 公司内外都存在许多错误,这源于这样一个事实,即如果你只是反复调用 super.clone 直到克隆了一个对象,那么你就拥有了一个浅层的对象副本。克隆通常与正在克隆的对象共享状态。如果该状态是可变的,则您没有两个独立的对象。如果您修改一个,另一个也会更改。突然之间,你会得到随机行为。
所以, 应该使用复制构造函数或复制工厂。
clone 无论是否实现 Cloneable 接口,此规则在被覆盖时都会引发问题。
不合规的代码示例
public class MyClass {
// …

public Object clone() { // Noncompliant
//…
}
}

合规解决方案
public class MyClass {
// …

MyClass (MyClass source) {
//…
}
}

参阅《复制构造函数与克隆》也可以参阅
S2157 –“Cloneables”应该实现“克隆”S1182 – 覆盖“clone”的类应为“Cloneable”并调用“super.clone()”
下面为引文翻译
Josh Bloch 谈设计与《Effective Java》作者的对话,Josh Bloch
作者 Bill Venners 首次在 JavaWorld 上发表,2002 年 1 月 4 日
复制构造函数与克隆
Bill Venners:在你的书中,你建议使用复制构造函数而不是实现 Cloneable 和编写 clone。你能详细说明吗?
Josh Bloch:如果你已经阅读了我的书中关于克隆的章节,特别是如果你看得仔细的话,你就会知道我认为克隆已经完全坏掉的东西。有一些设计缺陷,其中最大的一个是 Cloneable 接口没有 clone 方法。这意味着它根本不起作用:实现了 Cloneable 接口并不说明你可以用它做什么。相反,它说明了内部可能做些什么。它说如果通过 super.clone 反复调用它最终调用 Object 的 clone 方法,这个方法将返回原始的属性副本。
但它没有说明你可以用一个实现 Cloneable 接口的对象做什么,这意味着你不能做多态 clone 操作。如果我有一个 Cloneable 数组,你会认为我可以运行该数组并克隆每个元素以制作数组的深层副本,但不能。你不能强制转换对象为 Cloneable 接口并调用 clone 方法,因为 Cloneable 没有 public clone 方法,Object 类也没有。如果您尝试强制转换 Cloneable 并调用该 clone 方法,编译器会说您正在尝试在对象上调用受保护的 clone 方法。
事实的真相是,您不通过实施 Cloneable 和提供 clone 除复制能力之外的公共方法为您的客户提供任何能力。如果您提供具有不同名称的 copy 操作, 怎么也不次于去实现 Cloneable 接口。这基本上就是你用复制构造函数做的事情。复制构造方法有几个优点,我在本书中有讨论。一个很大的优点是可以使副本具有与原始副本不同的实现。例如,您可以将一个 LinkedList 复制到 ArrayList。
Object 的 clone 方法是非常棘手的。它基于属性复制,而且是“超语言”。它创建一个对象而不调用构造函数。无法保证它保留构造函数建立的不变量。多年来,在 Sun 内外存在许多错误,这源于这样一个事实,即如果你只是 super.clone 反复调用链直到你克隆了一个对象,那么你就拥有了一个浅层的对象副本。克隆通常与正在克隆的对象共享状态。如果该状态是可变的,则您没有两个独立的对象。如果您修改一个,另一个也会更改。突然之间,你会得到随机行为。
我使用的东西很少实现 Cloneable。我经常提供实现类的 clone 公共方法,仅是因为人们期望有。我没有抽象类实现 Cloneable,也没有接口扩展它,因为我不会将实现的负担 Cloneable 放在扩展(或实现)抽象类(或接口)的所有类上。这是一个真正的负担,几乎没有什么好处。
Doug Lea 走得更远。他告诉我 clone 除了复制数组之外他不再使用了。您应该使用 clone 复制数组,因为这通常是最快的方法。但 Doug 的类根本就不再实施 Cloneable 了。他放弃了。而且我认为这并非不合理。
这是一个耻辱, Cloneable 接口坏掉了,但它发生了。最初的 Java API 在紧迫的期限内完成,以满足市场窗口收紧的需求。最初的 Java 团队做了不可思议的工作,但并非所有的 API 都是完美的。Cloneable 是一个弱点,我认为人们应该意识到它的局限性。

退出移动版