Cloneable接口的目的是作为对象的一个mixin接口(mixin interface)(第20项),表明这样的类【的对象】是允许克隆的。遗憾的是,它没有达到这个目的。它的主要缺陷是缺少克隆方法,而Object的clone方法【的访问权限】是受保护【protect】的, 如果不采用反射(第65项),就不能仅仅因为它实现了Cloneable而在对象上调用clone方法。即使是反射调用也可能失败,因为无法保证对象具有可访问的clone方法。尽管存在这样那样的缺陷,这项设施仍然被广泛地使用着,因此值得我们去理解它。该项告诉您如何实现良好的clone方法,并讨论何时适合这样做,并提供替代方法。
既然Cloneable并没有包含任何方法,那么它到底有什么作用呢?它决定了Object中受保护的clone方法实现的行为:如果一个类实现了Cloneable,Object的clone方法将逐个返回该对象的拷贝字段,否则就会抛出CloneNotSupportedException。这是接口的一种极端非典型的用法,也不值得效仿。通常情况下,实现接口是为了表明类可以为它的客户做些什么。在这种情况下,它会修改超类上受保护方法的行为。
尽管规范中没有表明,实际上,实现Cloneable的类应该提供一个功能正常的公共克隆方法。 为了达到这个目的,类和它的所有超类都必须遵守一个相当复杂的、不可实施的,并且基本上没有文档说明的协议。由此得到一种脆弱的、危险的、语言之外的(extralinguistic)机制:无需调用构造器就可以创建对象。
clone方法的通用约定是非常脆弱的,下面是从Object规范中复制的:
创建和返回该对象的一个拷贝。这个“拷贝”的精确含义取决于该对象的类。一般的含义是,对于任何对象x,表达式x.clone() != x
将会是true,并且,表达式x.clone().getClass() == x.getClass()
将会返回true,但这些都不是绝对的要求。虽然通常情况下,表达式x.clone().equals(x)
将会是true,但是,这也不是一个绝对的要求。
按照惯例,此方法返回的对象应该通过调用super.clone来获取。 如果一个类及其所有超类(Object除外)都遵循这个约定,那就是这种情况:
x.clone().getClass() == x.getClass()
按照惯例,返回的对象应该独立于被克隆的对象。要实现这种独立性,可能需要在返回之前修改super.clone返回的对象的一个或多个字段。
这个机制与构造函数调用链类似(This mechanism is vaguely similar to constructor chaining)【就是子类的构造函数一定需要调用父类的构造函数】,它只是没有强制执行:如果一个类的clone方法返回一个不是通过super.clone而是通过调用构造函数获得的实例,编译器不会报错(complain),但是如果一个该类的子类调用super.clone,生成的对象将具有错误的类,从而阻止子类clone方法的正常运行。如果重写clone的类是final,则可以安全地忽略此约定,因为没有子类,所以不需要担心。但是如果一个final类有clone方法,但是该方法没有调用super.clone方法,那么该类没有理由实现Cloneable,因为它不依赖与Object的clone实现的行为。
假设你希望在一个类中实现Cloneable,并且它的超类都提供良好的clone方法。先调用super.clone。您获得的对象将是原始的完整功能的副本。在您的类中声明的任何字段将具有与原始值相同的值。如果每个字段都包含原始值或对不可变对象的引用,则返回的对象可能正是您所需要的,在这种情况下,不需要进一步处理。 例如,对于第11项中的PhoneNumber类就是这种情况,但请注意,不可变类应该永远不会提供clone方法,因为它只会鼓励浪费的复制(because it would merely encourage wasteful copying)【就是说复制了也没啥用,复制的每一个结果都是一样的,因为类是不可变的】。有了这个预告,下面是PhoneNumber的clone方法的样子:
// Clone method for class with no references to mutable state
@Override public PhoneNumber clone() {
try {
return (PhoneNumber) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // Can't happen
}
}
为了使此方法起作用,必须修改PhoneNumber的类声明以表明它实现了Cloneable。 虽然Object的clone方法返回Object,但此clone方法返回PhoneNumber。这样做是合法的,也是可取的,因为Java支持协变返回类型(covariant return types)。换句话说,重写方法的返回类型可以是被重写的子类方法的返回类型。这样客户端就不需要进行转换了(This eliminates the need for casting in the client.)。我们必须在返回之前将super.clone的结果从Object转换为PhoneNumber,但是这个转换保证是可以成功的(but the cast is guaranteed to succeed)。
对super.clone的调用包含在try-catch块中。 这是因为Object声明其clone方法抛出CloneNotSupportedException,这是一个经过检查的异常。 因为PhoneNumber实现了Cloneable,所以我们知道对super.clone的调用会成功。对这个样板文件的需求表明,CloneNotSupportedException应该是不受约束的(第71项)(The need for this boilerplate indicates that CloneNotSupportedException should have been unchecked (Item 71))。
如果对象包含引用可变对象的字段,使用上述这种简单的clone实现可能会导致灾难性的后果。例如,考虑到第7项中的Stack类:
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {