盡管克隆方法是在所有類最基本的Object中定義的,但克隆仍然不會在每個類裡自動進行。這似乎有些不可思議,因為基礎類方法在衍生類裡是肯定能用的。但Java確實有點兒反其道而行之;如果想在一個類裡使用克隆方法,唯一的辦法就是專門添加一些代碼,以便保證克隆的正常進行。
1. 使用protected時的技巧
為避免我們創建的每個類都默認具有克隆能力,clone()方法在基礎類Object裡得到了“保留”(設為protected)。這樣造成的後果就是:對那些簡單地使用一下這個類的客戶程序員來說,他們不會默認地擁有這個方法;其次,我們不能利用指向基礎類的一個句柄來調用clone()(盡管那樣做在某些情況下特別有用,比如用多形性的方式克隆一系列對象)。在編譯期的時候,這實際是通知我們對象不可克隆的一種方式——而且最奇怪的是,Java庫中的大多數類都不能克隆。因此,假如我們執行下述代碼:
Integer x = new Integer(l);
x = x.clone();
那麼在編譯期,就有一條討厭的錯誤消息彈出,告訴我們不可訪問clone()——因為Integer並沒有覆蓋它,而且它對protected版本來說是默認的)。
但是,假若我們是在一個從Object衍生出來的類中(所有類都是從Object衍生的),就有權調用Object.clone(),因為它是“protected”,而且我們在一個繼承器中。基礎類clone()提供了一個有用的功能——它進行的是對衍生類對象的真正“按位”復制,所以相當於標准的克隆行動。然而,我們隨後需要將自己的克隆操作設為public,否則無法訪問。總之,克隆時要注意的兩個關鍵問題是:幾乎肯定要調用super.clone(),以及注意將克隆設為public。
有時還想在更深層的衍生類中覆蓋clone(),否則就直接使用我們的clone()(現在已成為public),而那並不一定是我們所希望的(然而,由於Object.clone()已制作了實際對象的一個副本,所以也有可能允許這種情況)。protected的技巧在這裡只能用一次:首次從一個不具備克隆能力的類繼承,而且想使一個類變成“能夠克隆”。而在從我們的類繼承的任何場合,clone()方法都是可以使用的,因為Java不可能在衍生之後反而縮小方法的訪問范圍。換言之,一旦對象變得可以克隆,從它衍生的任何東西都是能夠克隆的,除非使用特殊的機制(後面討論)令其“關閉”克隆能力。
2. 實現Cloneable接口
為使一個對象的克隆能力功成圓滿,還需要做另一件事情:實現Cloneable接口。這個接口使人稍覺奇怪,因為它是空的!
interface Cloneable {}
之所以要實現這個空接口,顯然不是因為我們准備上溯造型成一個Cloneable,以及調用它的某個方法。有些人認為在這裡使用接口屬於一種“欺騙”行為,因為它使用的特性打的是別的主意,而非原來的意思。Cloneable interface的實現扮演了一個標記的角色,封裝到類的類型中。
兩方面的原因促成了Cloneable interface的存在。首先,可能有一個上溯造型句柄指向一個基礎類型,而且不知道它是否真的能克隆那個對象。在這種情況下,可用instanceof關鍵字(第11章有介紹)調查句柄是否確實同一個能克隆的對象連接:
if(myHandle instanceof Cloneable) // ...
第二個原因是考慮到我們可能不願所有對象類型都能克隆。所以Object.clone()會驗證一個類是否真的是實現了Cloneable接口。若答案是否定的,則“擲”出一個CloneNotSupportedException違例。所以在一般情況下,我們必須將“implement Cloneable”作為對克隆能力提供支持的一部分。