為消除複製能力,大家也許認為只需將clone()方法簡單地設為private(私人)即可,但這樣是行不通的,因為不能採用一個基礎類方法,並使其在衍生類中更“私人”。所以事情並沒有這麼簡單。此外,我們有必要控制一個對象是否能夠複製。對於我們設計的一個類,實際有許多種方案都是可以採取的:
(1) 保持中立,不為複製做任何事情。也就是說,儘管不可對我們的類複製,但從它繼承的一個類卻可根據實際情況決定複製。只有Object.clone()要對類中的欄位進行某些合理的操作時,才可以作這方面的決定。
(2) 支援clone(),採用實現Cloneable(可複製)能力的標準操作,並覆蓋clone()。在被覆蓋的clone()中,可調用super.clone(),並捕獲所有違例(這樣可使clone()不“擲”出任何違例)。
(3) 有條件地支援複製。若類容納了其他對象的控制代碼,而那些對象也許能夠複製(集合類便是這樣的一個例子),就可試著複製擁有對方控制代碼的所有對象;如果它們“擲”出了違例,只需讓這些違例通過即可。舉個例子來說,假設有一個特殊的Vector,它試圖複製自己容納的所有對象。編寫這樣的一個Vector時,並不知道客戶程式員會把什麼形式的對象置入這個Vector中,所以並不知道它們是否真的能夠複製。
(4) 不實現Cloneable(),但是將clone()覆蓋成protected,使任何欄位都具有正確的複製行為。這樣一來,從這個類繼承的所有東西都能覆蓋clone(),並調用super.clone()來產生正確的複製行為。注意在我們實現方案裡,可以而且應該調用super.clone()——即使那個方法本來預期的是一個Cloneable對象(否則會擲出一個違例),因為沒有人會在我們這種類型的對象上直接調用它。它只有通過一個衍生類調用;對那個衍生類來說,如果要保證它正常工作,需實現Cloneable。
(5) 不實現Cloneable來試著防止複製,並覆蓋clone(),以產生一個違例。為使這一設想順利實現,只有令從它衍生出來的任何類都調用重新定義後的clone()裡的suepr.clone()。
(6) 將類設為final,從而防止複製。若clone()尚未被我們的任何一個上級類覆蓋,這一設想便不會成功。若已被覆蓋,那麼再一次覆蓋它,並“擲”出一個CloneNotSupportedException(複製不支援)違例。為擔保複製被禁止,將類設為final是唯一的辦法。除此以外,一旦涉及保密對象或者遇到想對建立的對象數量進行控制的其他情況,應該將所有構建器都設為private,並提供一個或更多的特殊方法來建立對象。採用這種方式,這些方法就可以限制建立的對象數量以及它們的建立條件——一種特殊情況是第16章要介紹的singleton(獨子)方案。
下面這個例子總結了複製的各種實現方法,然後在階層中將其“關閉”:
//: CheckCloneable.java// Checking to see if a handle can be cloned// Can't clone this because it doesn't// override clone():class Ordinary {}// Overrides clone, but doesn't implement// Cloneable:class WrongClone extends Ordinary { public Object clone() throws CloneNotSupportedException { return super.clone(); // Throws exception }}// Does all the right things for cloning:class IsCloneable extends Ordinary implements Cloneable { public Object clone() throws CloneNotSupportedException { return super.clone(); }}// Turn off cloning by throwing the exception:class NoMore extends IsCloneable { public Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); }}class TryMore extends NoMore { public Object clone() throws CloneNotSupportedException { // Calls NoMore.clone(), throws exception: return super.clone(); }}class BackOn extends NoMore { private BackOn duplicate(BackOn b) { // Somehow make a copy of b // and return that copy. This is a dummy // copy, just to make the point: return new BackOn(); } public Object clone() { // Doesn't call NoMore.clone(): return duplicate(this); }}// Can't inherit from this, so can't override// the clone method like in BackOn:final class ReallyNoMore extends NoMore {}public class CheckCloneable { static Ordinary tryToClone(Ordinary ord) { String id = ord.getClass().getName(); Ordinary x = null; if(ord instanceof Cloneable) { try { System.out.println("Attempting " + id); x = (Ordinary)((IsCloneable)ord).clone(); System.out.println("Cloned " + id); } catch(CloneNotSupportedException e) { System.out.println( "Could not clone " + id); } } return x; } public static void main(String[] args) { // Upcasting: Ordinary[] ord = { new IsCloneable(), new WrongClone(), new NoMore(), new TryMore(), new BackOn(), new ReallyNoMore(), }; Ordinary x = new Ordinary(); // This won't compile, since clone() is // protected in Object: //! x = (Ordinary)x.clone(); // tryToClone() checks first to see if // a class implements Cloneable: for(int i = 0; i < ord.length; i++) tryToClone(ord[i]); }} ///:~
第一個類Ordinary代表著大家在本書各處最常見到的類:不支援複製,但在它正式應用以後,卻也不禁止對其複製。但假如有一個指向Ordinary對象的控制代碼,而且那個對象可能是從一個更深的衍生類上溯造型來的,便不能判斷它到底能不能複製。
WrongClone類揭示了實現複製的一種不正確途徑。它確實覆蓋了Object.clone(),並將那個方法設為public,但卻沒有實現Cloneable。所以一旦發出對super.clone()的調用(由於對Object.clone()的一個調用造成的),便會無情地擲出CloneNotSupportedException違例。
在IsCloneable中,大家看到的才是進行複製的各種正確行動:先覆蓋clone(),並實現了Cloneable。但是,這個clone()方法以及本例的另外幾個方法並不捕獲CloneNotSupportedException違例,而是任由它通過,並傳遞給調用者。隨後,調用者必須用一個try-catch代碼塊把它包圍起來。在我們自己的clone()方法中,通常需要在clone()內部捕獲CloneNotSupportedException違例,而不是任由它通過。正如大家以後會理解的那樣,對這個例子來說,讓它通過是最正確的做法。
類NoMore試圖按照Java設計者打算的那樣“關閉”複製:在衍生類clone()中,我們擲出CloneNotSupportedException違例。TryMore類中的clone()方法正確地調用super.clone(),並解析成NoMore.clone(),後者擲出一個違例並禁止複製。
但在已被覆蓋的clone()方法中,假若程式員不遵守調用super.clone()的“正確”方法,又會出現什麼情況呢?在BackOn中,大家可看到實際會發生什麼。這個類用一個獨立的方法duplicate()製作當前對象的一個副本,並在clone()內部調用這個方法,而不是調用super.clone()。違例永遠不會產生,而且新類是可以複製的。因此,我們不能依賴“擲”出一個違例的方法來防止產生一個可複製的類。唯一安全的方法在ReallyNoMore中得到了示範,它設為final,所以不可繼承。這意味著假如clone()在final類中擲出了一個違例,便不能通過繼承來進行修改,並可有效地禁止複製(不能從一個擁有任意繼承級數的類中明確調用Object.clone();只能調用super.clone(),它只可訪問直接基礎類)。因此,只要製作一些涉及安全問題的對象,就最好把那些類設為final。
在類CheckCloneable中,我們看到的第一個類是tryToClone(),它能接納任何Ordinary對象,並用instanceof檢查它是否能夠複製。若答案是肯定的,就將對象造型成為一個IsCloneable,調用clone(),並將結果造型回Ordinary,最後捕獲有可能產生的任何違例。請注意用運行期類型評鑑(見第11章)列印出類名,使自己看到發生的一切情況。
在main()中,我們建立了不同類型的Ordinary對象,並在數組定義中上溯造型成為Ordinary。在這之後的頭兩行代碼建立了一個純粹的Ordinary對象,並試圖對其複製。然而,這些代碼不會得到編譯,因為clone()是Object中的一個protected(受到保護的)方法。代碼剩餘的部分將遍曆數組,並試著複製每個對象,分別報告它們的成功或失敗。輸出如下:
Attempting IsCloneableCloned IsCloneableAttempting NoMoreCould not clone NoMoreAttempting TryMoreCould not clone TryMoreAttempting BackOnCloned BackOnAttempting ReallyNoMoreCould not clone ReallyNoMore
總之,如果希望一個類能夠複製,那麼:
(1) 實現Cloneable介面
(2) 覆蓋clone()
(3) 在自己的clone()中調用super.clone()
(4) 在自己的clone()中捕獲違例
這一系列步驟能達到最理想的效果。