Cloneable接口的目的是作为对象的一个mixin接口,表明对象允许克隆;但这个目的没有达到。
其主要缺点是,Cloneable缺少一个clone()方法,而Object.clone()是受保护的。
通常,实现接口是为了表明类可以为它的客户做些什么;而Cloneable却是改变了超类中受保护方法的行为。
Object.clone()定义的约定:
/** 创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。一般来说,对于任何对象 x,如果表达式: x.clone() != x 是正确的,则表达式: x.clone().getClass() == x.getClass()将为 true, 但这些不是绝对条件。一般情况下是: x.clone().equals(x)将为 true,但这不是绝对条件。 --按照惯例,返回的对象应该通过调用 super.clone 获得。 如果一个类及其所有的超类(Object 除外)都遵守此约定,则 x.clone().getClass() == x.getClass()。 --按照惯例,此方法返回的对象应该独立于该对象(正被克隆的对象)。 要获得此独立性,在 super.clone 返回对象之前,有必要对该对象的一个或多个字段进行修改。 这通常意味着要复制包含正在被克隆对象的内部“深层结构”的所有可变对象,并使用对副本的引用替换对这些对象的引用。 如果一个类只包含基本字段或对不变对象的引用,那么通常不需要修改 super.clone 返回的对象中的字段。 Object 类的 clone 方法执行特定的克隆操作。 首先,如果此对象的类不能实现接口 Cloneable,则会抛出 CloneNotSupportedException。 注意:所有的数组都被视为实现接口 Cloneable。 否则,此方法会创建此对象的类的一个新实例,并像通过分配那样,严格使用此对象相应字段的内容初始化该对象的所有字段; 这些字段的内容没有被自我克隆。所以,此方法执行的是该对象的“浅表复制”,而不“深层复制”操作。 Object 类本身不实现接口 Cloneable,所以在类为 Object 的对象上调用 clone 方法将会导致在运行时抛出异常。 */ protected native Object clone() throws CloneNotSupportedException;
所有实现了Cloneable接口的类,都应该提供一个public的clone()方法;
在这个clone()方法中,首先调用super.clone(),然后修正任何需要修正的域。
例如Hashtable.clone()
/** * Creates a shallow copy of this hashtable. All the structure of the * hashtable itself is copied, but the keys and values are not cloned. * This is a relatively expensive operation. * * @return a clone of the hashtable */ public synchronized Object clone() { try { Hashtable<K,V> t = (Hashtable<K,V>) super.clone(); // --super.clone() t.table = new Entry[table.length]; for (int i = table.length ; i-- > 0 ; ) { t.table[i] = (table[i] != null) ? (Entry<K,V>) table[i].clone() : null; // --递归调用实例变量.clone() } t.keySet = null; t.entrySet = null; t.values = null; t.modCount = 0; return t; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } }注意,递归调用实例变量.clone()时,如果该变量为final,则不行! clone架构与饮用可变对象的final域的正常用法是不兼容的!
另一个实现对象拷贝的好办法是提供一个拷贝构造器(copy constructor)或者拷贝工厂(copy factory)。
例如:
public Yum(Yum yum); public static Yum newInstance(Yum yum);
另外,拷贝构造器和拷贝工厂可以带参数,参数类型一般是该类实现的接口,以便用户选择拷贝的实现类型。
例如,有一个HashSet s,希望把它拷贝成一个TreeSet,可以调用:new TreeSet(s)
/** * Constructs a new tree set containing the elements in the specified * collection, sorted according to the <i>natural ordering</i> of its * elements. All elements inserted into the set must implement the * {@link Comparable} interface. Furthermore, all such elements must be * <i>mutually comparable</i>: {@code e1.compareTo(e2)} must not throw a * {@code ClassCastException} for any elements {@code e1} and * {@code e2} in the set. * * @param c collection whose elements will comprise the new set * @throws ClassCastException if the elements in {@code c} are * not {@link Comparable}, or are not mutually comparable * @throws NullPointerException if the specified collection is null */ public TreeSet(Collection<? extends E> c) { this(); addAll(c); }