谨慎地重写 clone 方法

Cloneable 接口的目的是作为一个 mixin 接口 ,公布这样的类允许克隆。

不幸的是,它没有达到这个目的。它的主要缺点是缺少 clone 方法,而 Object 的 clone 方法是受保护的。

你不能,不借助反射,仅仅因为它实现了 Cloneable 接口,就调用对象上的 clone 方法。

即使是反射调用也可能失败,因为不能保证对象具有可访问的 clone 方法。

Cloneable 接口决定了 Object 的受保护的 clone 方法实现的行为:如果一个类实现了 Cloneable 接口,那么 Object 的 clone 方法将返回该对象的逐个属性(field-by-field)拷贝;否则会抛出 CloneNotSupportedException 异常。

这是一个非常反常的接口使用,而不应该被效仿。 通常情况下,实现一个接口用来表示可以为客户做什么。但对于 Cloneable 接口,它会修改父类上受保护方法的行为。

普通克隆

假设你希望在一个类中实现 Cloneable 接口,它的父类提供了一个行为良好的 clone 方法。首先调用 super.clone。 得到的对象将是原始的完全功能的复制品。 在你的类中声明的任何属性将具有与原始属性相同的值。 如果每个属性包含原始值或对不可变对象的引用,则返回的对象可能正是你所需要的,在这种情况下,不需要进一步的处理。

示例代码Item13Example01.java:实现 Cloneable 接口的 clone 方法。

对象包含引用可变对象的属性

如果对象包含引用可变对象的属性,则前面显示的简单 clone 实现可能是灾难性的。

克隆 final 属性

如果属性是 final 的,则以前的解决方案将不起作用,因为克隆将被禁止向该属性分配新的值。

这是一个基本的问题:像序列化一样,Cloneable 体系结构与引用可变对象的 final 属性的正常使用不兼容,除非可变对象可以在对象和其克隆之间安全地共享。

为了使一个类可以克隆,可能需要从一些属性中移除 final 修饰符。

复制构造方法或复制工厂

对象复制更好的方法是提供一个复制构造方法或复制工厂。

复制构造方法接受参数,其类型为包含此构造方法的类,例如:

// 复制构造方法
public Yum(Yum yum) { ... };
// 复制工厂
public static Yum newInstance(Yum yum) { ... };

复制构造方法及其静态工厂变体与 Cloneable/clone 相比有许多优点:

此外,复制构造方法或复制工厂可以接受类型为该类实现的接口的参数。 例如,按照惯例,所有通用集合实现都提供了一个构造方法,其参数的类型为 Collection 或 Map。 基于接口的复制构造方法和复制工厂(更适当地称为转换构造方法和转换工厂)允许客户端选择复制的实现类型,而不是强制客户端接受原始实现类型。 例如,假设你有一个 HashSet,并且你想把它复制为一个 TreeSet。 clone 方法不能提供这种功能,但使用转换构造方法很容易:new TreeSet<>(s)

考虑到与 Cloneable 接口相关的所有问题,新的接口不应该继承它,新的可扩展类不应该实现它。 虽然实现 Cloneable 接口对于 final 类没有什么危害,但应该将其视为性能优化的角度,仅在极少数情况下才是合理的。

通常,复制功能最好由构造方法或工厂提供。 这个规则的一个明显的例外是数组,它最好用 clone 方法复制。