覆蓋clone時需要實現Cloneable接口,Cloneable並沒有定義任何方法。
那Cloneable的意義是什麼?
如果一個類實現了Clonable,Object的clone方法就可以返回該對象的逐域拷貝,否則會拋出CloneNotSupportedException。
通常,實現接口是為了表明類的行為。
而Cloneable接口改變了超類中protected方法的行為。
這是種非典型用法,不值得仿效。
好了,既然覆蓋了clone方法,我們需要遵守一些約定:
另外,我們必須保證clone結果不能影響原始對象的同時保證clone方法的約定。
比如下面這種情況,沒有覆蓋clone方法,直接得到super.clone()的結果:
import java.util.Arrays;
public class Stack implements Cloneable {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
public boolean isEmpty() {
return size == 0;
}
// Ensure space for at least one more element.
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
結果可想而知,clone結果的elements和原始對象的elements引用同一個數組。
既然如此,覆蓋clone方法,並保證不會傷害到原始對象:
@Override
public Stack clone() {
try {
Stack result = (Stack) super.clone();
result.elements = elements.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
雖然把elements單獨拿出來clone了一遍,但這種做法的前提是elements不是final。
其實再正常不過,clone無法和引用可變對象的不可變field兼容。
如果數組的元素是引用類型,當某個元素發生改變時仍然會出現問題。
此處以Hashtable為例,Hashtable中的元素用其內部類Entry。
private static class Entry<K,V> implements Map.Entry<K,V> {
int hash;
final K key;
V value;
Entry<K,V> next;
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
//..
}
如果像Stack例子中那樣直接對elements進行clone,某個Entry發生變化時clone出來的Hashtable也隨之發生變化。
於是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();
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;
}
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會導致諸多問題,有兩點建議: