調用Object.clone()時,實際發生的是什麼事情呢?當我們在自己的類裡覆蓋clone()時,什麼東西對於super.clone()來說是最關鍵的呢?根類中的clone()方法負責建立正確的存儲容量,並通過“按位復制”將二進制位從原始對象中復制到新對象的存儲空間。也就是說,它並不只是預留存儲空間以及復制一個對象——實際需要調查出欲復制之對象的准確大小,然後復制那個對象。由於所有這些工作都是在由根類定義之clone()方法的內部代碼中進行的(根類並不知道要從自己這裡繼承出去什麼),所以大家或許已經猜到,這個過程需要用RTTI判斷欲克隆的對象的實際大小。采取這種方式,clone()方法便可建立起正確數量的存儲空間,並對那個類型進行正確的按位復制。
不管我們要做什麼,克隆過程的第一個部分通常都應該是調用super.clone()。通過進行一次准確的復制,這樣做可為後續的克隆進程建立起一個良好的基礎。隨後,可采取另一些必要的操作,以完成最終的克隆。
為確切了解其他操作是什麼,首先要正確理解Object.clone()為我們帶來了什麼。特別地,它會自動克隆所有句柄指向的目標嗎?下面這個例子可完成這種形式的檢測:
//: Snake.java // Tests cloning to see if destination of // handles are also cloned. public class Snake implements Cloneable { private Snake next; private char c; // Value of i == number of segments Snake(int i, char x) { c = x; if(--i > 0) next = new Snake(i, (char)(x + 1)); } void increment() { c++; if(next != null) next.increment(); } public String toString() { String s = ":" + c; if(next != null) s += next.toString(); return s; } public Object clone() { Object o = null; try { o = super.clone(); } catch (CloneNotSupportedException e) {} return o; } public static void main(String[] args) { Snake s = new Snake(5, 'a'); System.out.println("s = " + s); Snake s2 = (Snake)s.clone(); System.out.println("s2 = " + s2); s.increment(); System.out.println( "after s.increment, s2 = " + s2); } } ///:~
一條Snake(蛇)由數段構成,每一段的類型都是Snake。所以,這是一個一段段鏈接起來的列表。所有段都是以循環方式創建的,每做好一段,都會使第一個構建器參數的值遞減,直至最終為零。而為給每段賦予一個獨一無二的標記,第二個參數(一個Char)的值在每次循環構建器調用時都會遞增。
increment()方法的作用是循環遞增每個標記,使我們能看到發生的變化;而toString則循環打印出每個標記。輸出如下:
s = :a:b:c:d:e s2 = :a:b:c:d:e after s.increment, s2 = :a:c:d:e:f
這意味著只有第一段才是由Object.clone()復制的,所以此時進行的是一種“淺層復制”。若希望復制整條蛇——即進行“深層復制”——必須在被覆蓋的clone()裡采取附加的操作。
通常可在從一個能克隆的類裡調用super.clone(),以確保所有基礎類行動(包括Object.clone())能夠進行。隨著是為對象內每個句柄都明確調用一個clone();否則那些句柄會別名變成原始對象的句柄。構建器的調用也大致相同——首先構造基礎類,然後是下一個衍生的構建器……以此類推,直到位於最深層的衍生構建器。區別在於clone()並不是個構建器,所以沒有辦法實現自動克隆。為了克隆,必須由自己明確進行。