1、概述
如果clone方法返回一个由构造器创建的对象,它就得到有错误的类。因此,如果覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone得到的对象。
如果类的所有超类都遵循这条规则,那么调用super.clone最终会调用Object的clone方法,从而创建出正确的实例。这种机制类似于自动的构造器调用链,只不过它不是强制要求的。
2、示例
需求: 为HashTable编写一个clone方法,它内部数据包含一个HashTable数组,每个HashTable都指向键值对列表的第一个项,如果table是空的,返回null。
代码实现如下:
public class HashTable implements Cloneable { private Entry[] buckets; private static class Entry{ Object key; Object value; Entry next; Entry(Object key, Object value, Entry next) { this.key = key; this.value = value; this.next = next; } } //...... } //递归clone这个HashTable数组,如下: public HashTable clone(){ HashTable result = new HashTable(); try { result = (HashTable) super.clone(); result.buckets = buckets.clone(); return result; } catch (CloneNotSupportedException e) { e.printStackTrace(); } return result; }
虽然被clone对象有它自己的HashTable数组,但这个数组引用的链表与原始对象是一样的,从而很容易引起clone对象和原始对象中的不确定的行为,为了修正这个问题,必须单独的拷贝并组成每个桶的链表,实现如下:
public class HashTable1 implements Cloneable { private Entry[] buckets; private static class Entry{ Object key; Object value; Entry next; Entry(Object key, Object value, Entry next) { this.key = key; this.value = value; this.next = next; } Entry deepCopy(){ Entry result = new Entry(key,value,next); for(Entry p=result;p.next !=null; p=p.next){ p.next=new Entry(p.next.key,p.next.value,p.next.next); } return result; } } //...... public HashTable1 clone(){ HashTable1 result = new HashTable1(); try { result = (HashTable1) super.clone(); result.buckets = new Entry[buckets.length]; for(int i=0;i<buckets.length;i++){ result.buckets[i] = buckets[i].deepCopy(); } return result; } catch (CloneNotSupportedException e) { e.printStackTrace(); } return result; } }
步骤:先调用super.clone方法,然后把结果对象中的所有域都设置称他们的空白状态,然后调用高层的方法来重新产生对象的状态。
3、总结
简而言之,所有实现了Cloneable接口的类都应该用一个公有的方法覆盖clone,此方法首先调用super.clone,然后修正任何需要修正的域。一般情况下,这意味着要拷贝任何包含内部“深层结构”的可变对象,并用指向新对象的引用代替原来指向这些对象的引用。