Cloneable接口的目的是作為對(duì)象的mixin接口(mixin interface),表明這樣的對(duì)象允許克隆(clone)。
不能僅僅因?yàn)橐粋€(gè)對(duì)象實(shí)現(xiàn)了Cloneable,就可以調(diào)用clone方法。
實(shí)現(xiàn)接口是為了表明類可以為客戶做些什么,然而對(duì)于Cloneable接口,它改變了超類中受保護(hù)的方法行為。
1. Cloneable接口的作用
該接口決定了Object中受保護(hù)的clone方法的實(shí)現(xiàn)行為:
- 如果一個(gè)類實(shí)現(xiàn)了Cloneable接口,Object的clone方法返回該對(duì)象的逐域拷貝;
- 如果一個(gè)類未實(shí)現(xiàn)Cloneable接口,則該對(duì)象就會(huì)拋出CloneNotSupportedException異常。
2. clone方法的約定
- clone方法的約定通常是非常弱的,以下要求在clone方法中都不是絕對(duì)要求:
x.clone() != x; //將會(huì)是true
x.clone().getClass() == x.getClass(); //將會(huì)是true
x.clone().equals(x); //將會(huì)是true
- 拷貝對(duì)象往往會(huì)導(dǎo)致創(chuàng)建它的類的一個(gè)新實(shí)例,但它同時(shí)也會(huì)要求拷貝內(nèi)部的數(shù)據(jù)結(jié)構(gòu),在這個(gè)過程中不許調(diào)用構(gòu)造器。
3. 實(shí)現(xiàn)clone方法
- 超類能夠提供克隆功能的唯一途徑是返回一個(gè)通過調(diào)用super.clone而得到的對(duì)象;
- 如果覆蓋了非final類中的clone方法,則應(yīng)該返回一個(gè)通過調(diào)用super.clone而得到的對(duì)象,如果類所有的超類都遵守這條規(guī)則那么調(diào)用super.clone最終會(huì)調(diào)用Object的clone方法,從而創(chuàng)造出正確類的實(shí)例。(機(jī)制為自動(dòng)調(diào)用鏈,非強(qiáng)制要求)
- 如果待克隆的域僅僅包含一個(gè)基本類型的值,或者包含一個(gè)指向不可變對(duì)象的引用,則被返回的對(duì)象可能是正需要的對(duì)象,如果不是,需要另作處理
4. 非基本類型clone
- Stack中的數(shù)組
public class Stack {
private Object[] elements;
}
// clone時(shí)需要拷貝棧的內(nèi)部信息,
// elements數(shù)組遞歸調(diào)用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()的結(jié)果轉(zhuǎn)換成Object[]。
// 自Java1.5發(fā)行版起,在數(shù)組上調(diào)用clone返回?cái)?shù)組的數(shù)組,其編譯時(shí)類型與被克隆數(shù)組的類型相同
// 注:如果elements域是final的,上述方案就不能正常工作,因?yàn)閏lone方法是被禁止給elements域賦新值
// (clone架構(gòu)與引用可變對(duì)象的final域的正常使用時(shí)不兼容的)
- 散列桶中的數(shù)組
正在為一個(gè)散列表編寫clone方法,它的內(nèi)部數(shù)據(jù)包含一個(gè)散列桶數(shù)組,每個(gè)散列桶都指向“鍵-值”對(duì)鏈表的第一個(gè)項(xiàng),如果桶是空的,則為null。該類如下:
public class HashTable implements Cloneable {
private Entry[] buckets = ...;
private static class Entry {
final Object key;
Object value;
Entry next;
Entry (Object key, Object value, Entry next) {
this.key = key;
this.value = value;
}
}
}
// clone時(shí)不僅需要遞歸的克隆這個(gè)散列桶數(shù)組,
// 而且需要單獨(dú)的拷貝并組成每個(gè)桶的列表,下面是常用做法
@Override
public HashTable clone() {
try {
HashTable result = (HashTable) super.clone();
result.buckets = new Entry[buckets.length];
for (int i = 0; i < buckets.length; i++) {
if (buckets[i] != null) {
result.buckets[i] = buckets[i].deepCopy();
}
}
} catch (CloneNotSupportException e) {
throw new AssertionError();
}
}
// 私有類的HashTable.Entry被加強(qiáng)了,支持一個(gè)“深拷貝(deep copy)”方法
// 防止因鏈表過長(zhǎng)導(dǎo)致棧溢出,可采用迭代(Iteration)的方法代替遞歸(recursion)
// Iteratively copy the linked list headed by this Entry
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;
}
復(fù)雜對(duì)象的克隆
先調(diào)用super.clone,然后把結(jié)果對(duì)象中的所有域設(shè)置成他們的空白狀態(tài)(virgin state),然后調(diào)用高層的方法來重新產(chǎn)生對(duì)象的狀態(tài)。這種做法往往會(huì)產(chǎn)生一個(gè)簡(jiǎn)單、合理且相當(dāng)優(yōu)美的clone方法,但通常沒有直接操作對(duì)象及其克隆對(duì)象的內(nèi)部狀態(tài)的clone方法快。總而言之,實(shí)現(xiàn)了Cloneable接口的類都應(yīng)該用一個(gè)共有的方法覆蓋clone。此公有方法首先調(diào)用super.clone,然后修正任何需要修改的域。
5. 代替方法
- 另外實(shí)現(xiàn)對(duì)象拷貝的好辦法是提供一個(gè)拷貝構(gòu)造器(copy constructor)或拷貝工廠(copy factory)??截悩?gòu)造器只是一個(gè)構(gòu)造器,它唯一的參數(shù)類型是包含該構(gòu)造器的類。如:
public Yum(Yum yum);
// 拷貝工廠類似于拷貝構(gòu)造器的靜態(tài)工廠:
public static Yum newInstance(Yum yum);
- 拷貝構(gòu)造器的優(yōu)點(diǎn)
- 其不依賴于某一種很有風(fēng)險(xiǎn)的、語(yǔ)言之外的對(duì)象創(chuàng)建機(jī)制;
- 其不遵守尚未制定好的文檔規(guī)范;
- 其不會(huì)與final域的正常使用發(fā)生沖突;
- 其不會(huì)拋出不必要的受檢查異常;
- 其不需要類型轉(zhuǎn)換;
- 采用其代替clone方法時(shí),并沒有放棄接口功能特性。
注:對(duì)于一個(gè)專門為了繼承而設(shè)計(jì)的類,如果你未能提供行為良好的受保護(hù)的clone方法,他的子類就不能實(shí)現(xiàn)Cloneable接口。