內(nèi)容摘要
若需修改一個對象,同時不想改變調(diào)用者的對象,就要制作該對象的一個本地副本,
java克隆對象clone()的用法和作用
。這也是本地副本最常見的一種用途。若決定制作一個本地副本,只需簡單地使用clone()方法即可。Clone是“克隆”的意思,即制作完全一模一樣的副本。這個方法在基礎(chǔ)類Object中定義成“protected”(受保護(hù))模式。但在希望克隆的任何衍生類中,必須將其覆蓋為“public”模式。例如,標(biāo)準(zhǔn)庫類Vector覆蓋了clone(),所以能為Vector調(diào)用clone(),<code class="hljs" php="">寫clone()方法時,通常都有一行代碼 super.clone();clone 有缺省行為,super.clone();因?yàn)槭紫纫迅割愔械某蓡T復(fù)制到位,然后才是復(fù)制自己的成員。</code>
java克隆對象
若需修改一個對象,同時不想改變調(diào)用者的對象,就要制作該對象的一個本地副本。這也是本地副本最常見的一種用途。若決定制作一個本地副本,只需簡單地使用clone()方法即可。Clone是“克隆”的意思,即制作完全一模一樣的副本。這個方法在基礎(chǔ)類Object中定義成“protected”(受保護(hù))模式。但在希望克隆的任何衍生類中,必須將其覆蓋為“public”模式。例如,標(biāo)準(zhǔn)庫類Vector覆蓋了clone(),所以能為Vector調(diào)用clone(),如下所示:
<code class="hljs" cs="">import java.util.*;class Int { private int i; public Int(int ii) { i = ii; } public void increment() { i++; } public String toString() { return Integer.toString(i); }}public class Cloning { public static void main(String[] args) { Vector v = new Vector(); for(int i = 0; i < 10; i++ ) v.addElement(new Int(i)); System.out.println(v: + v); Vector v2 = (Vector)v.clone(); for(Enumeration e = v2.elements(); e.hasMoreElements(); ) ((Int)e.nextElement()).increment(); System.out.println(v: + v); }}</code>
clone()方法產(chǎn)生了一個Object,后者必須立即重新造型為正確類型。這個例子指出Vector的clone()方法不能自動嘗試克隆Vector內(nèi)包含的每個對象——由于別名問題,老的Vector和克隆的Vector都包含了相同的對象。我們通常把這種情況叫作“簡單復(fù)制”或者“淺層復(fù)制”,因?yàn)樗粡?fù)制了一個對象的“表面”部分。實(shí)際對象除包含這個“表面”以外,還包括句柄指向的所有對象,以及那些對象又指向的其他所有對象,由此類推。這便是“對象網(wǎng)”或“對象關(guān)系網(wǎng)”的由來。若能復(fù)制下所有這張網(wǎng),便叫作“全面復(fù)制”或者“深層復(fù)制”。
在輸出中可看到淺層復(fù)制的結(jié)果,注意對v2采取的行動也會影響到v:
<code class="hljs" http="">v: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]v: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]</code>
一般來說,由于不敢保證Vector里包含的對象是“可以克隆”(注釋②)的,所以最好不要試圖克隆那些對象。
使類具有克隆能力
盡管克隆方法是在所有類最基本的Object中定義的,但克隆仍然不會在每個類里自動進(jìn)行。這似乎有些不可思議,因?yàn)榛A(chǔ)類方法在衍生類里是肯定能用的。但Java確實(shí)有點(diǎn)兒反其道而行之;如果想在一個類里使用克隆方法,唯一的辦法就是專門添加一些代碼,以便保證克隆的正常進(jìn)行。
使用protected時的技巧
為避免我們創(chuàng)建的每個類都默認(rèn)具有克隆能力,clone()方法在基礎(chǔ)類Object里得到了“保留”(設(shè)為protected)。這樣造成的后果就是:對那些簡單地使用一下這個類的客戶程序員來說,他們不會默認(rèn)地?fù)碛羞@個方法;其次,我們不能利用指向基礎(chǔ)類的一個句柄來調(diào)用clone()(盡管那樣做在某些情況下特別有用,比如用多形性的方式克隆一系列對象)。在編譯期的時候,這實(shí)際是通知我們對象不可克隆的一種方式——而且最奇怪的是,Java庫中的大多數(shù)類都不能克隆。因此,假如我們執(zhí)行下述代碼:
Integer x = new Integer(l);
x = x.clone();
那么在編譯期,就有一條討厭的錯誤消息彈出,告訴我們不可訪問clone()——因?yàn)镮nteger并沒有覆蓋它,而且它對protected版本來說是默認(rèn)的)。
但是,假若我們是在一個從Object衍生出來的類中(所有類都是從Object衍生的),就有權(quán)調(diào)用Object.clone(),因?yàn)樗?ldquo;protected”,而且我們在一個繼承器中;A(chǔ)類clone()提供了一個有用的功能——它進(jìn)行的是對衍生類對象的真正“按位”復(fù)制,所以相當(dāng)于標(biāo)準(zhǔn)的克隆行動。然而,我們隨后需要將自己的克隆操作設(shè)為public,否則無法訪問?傊,克隆時要注意的兩個關(guān)鍵問題是:幾乎肯定要調(diào)用super.clone(),以及注意將克隆設(shè)為public。
有時還想在更深層的衍生類中覆蓋clone(),否則就直接使用我們的clone()(現(xiàn)在已成為public),而那并不一定是我們所希望的(然而,由于Object.clone()已制作了實(shí)際對象的一個副本,所以也有可能允許這種情況)。protected的技巧在這里只能用一次:首次從一個不具備克隆能力的類繼承,而且想使一個類變成“能夠克隆”。而在從我們的類繼承的任何場合,clone()方法都是可以使用的,因?yàn)镴ava不可能在衍生之后反而縮小方法的訪問范圍。換言之,一旦對象變得可以克隆,從它衍生的任何東西都是能夠克隆的,除非使用特殊的機(jī)制(后面討論)令其“關(guān)閉”克隆能力。
實(shí)現(xiàn)Cloneable接口
為使一個對象的克隆能力功成圓滿,還需要做另一件事情:實(shí)現(xiàn)Cloneable接口。這個接口使人稍覺奇怪,因?yàn)樗强盏模?/p>
interface Cloneable {}
之所以要實(shí)現(xiàn)這個空接口,顯然不是因?yàn)槲覀儨?zhǔn)備上溯造型成一個Cloneable,以及調(diào)用它的某個方法。有些人認(rèn)為在這里使用接口屬于一種“欺騙”行為,因?yàn)樗褂玫奶匦源虻氖莿e的主意,而非原來的意思。Cloneable interface的實(shí)現(xiàn)扮演了一個標(biāo)記的角色,封裝到類的類型中。
兩方面的原因促成了Cloneable interface的存在。首先,可能有一個上溯造型句柄指向一個基礎(chǔ)類型,而且不知道它是否真的能克隆那個對象。在這種情況下,可用instanceof關(guān)鍵字(第11章有介紹)調(diào)查句柄是否確實(shí)同一個能克隆的對象連接:
if(myHandle instanceof Cloneable) // …
第二個原因是考慮到我們可能不愿所有對象類型都能克隆。所以O(shè)bject.clone()會驗(yàn)證一個類是否真的是實(shí)現(xiàn)了Cloneable接口。若答案是否定的,則“擲”出一個CloneNotSupportedException違例。所以在一般情況下,我們必須將“implement Cloneable”作為對克隆能力提供支持的一部分。
java的clone實(shí)現(xiàn)
理解了實(shí)現(xiàn)clone()方法背后的所有細(xì)節(jié)后,便可創(chuàng)建出能方便復(fù)制的類,以便提供了一個本地副本:
<code class="hljs" cs="">import java.util.*;class MyObject implements Cloneable { int i; MyObject(int ii) { i = ii; } public Object clone() { Object o = null; try { o = super.clone(); } catch (CloneNotSupportedException e) { System.out.println(MyObject can't clone); } return o; } public String toString() { return Integer.toString(i); }}public class LocalCopy { static MyObject g(MyObject v) { v.i++; return v; } static MyObject f(MyObject v) { v.i++; return v; } public static void main(String[] args) { MyObject a = new MyObject(11); MyObject b = g(a); if(a == b) System.out.println(a == b); else System.out.println(a != b); System.out.println(a = + a); System.out.println(b = + b); MyObject c = new MyObject(47); MyObject d = f(c); if(c == d) System.out.println(c == d); else System.out.println(c != d); System.out.println(c = + c); System.out.println(d = + d); }}</code>
不管怎樣,clone()必須能夠訪問,所以必須將其設(shè)為public(公共的)。其次,作為clone()的初期行動,應(yīng)調(diào)用clone()的基礎(chǔ)類版本。這里調(diào)用的clone()是Object內(nèi)部預(yù)先定義好的。之所以能調(diào)用它,是由于它具有protected(受到保護(hù)的)屬性,所以能在衍生的類里訪問。
Object.clone()會檢查原先的對象有多大,再為新對象騰出足夠多的內(nèi)存,將所有二進(jìn)制位從原來的對象復(fù)制到新對象。這叫作“按位復(fù)制”,而且按一般的想法,這個工作應(yīng)該是由clone()方法來做的。但在Object.clone()正式開始操作前,首先會檢查一個類是否Cloneable,即是否具有克隆能力——換言之,它是否實(shí)現(xiàn)了Cloneable接口。若未實(shí)現(xiàn),Object.clone()就擲出一個CloneNotSupportedException違例,指出我們不能克隆它。因此,我們最好用一個try-catch塊將對super.clone()的調(diào)用代碼包圍(或封裝)起來,試圖捕獲一個應(yīng)當(dāng)永不出現(xiàn)的違例(因?yàn)檫@里確實(shí)已實(shí)現(xiàn)了Cloneable接口)。
在LocalCopy中,兩個方法g()和f()揭示出兩種參數(shù)傳遞方法間的差異。其中,g()演示的是按引用傳遞,它會修改外部對象,并返回對那個外部對象的一個引用。而f()是對自變量進(jìn)行克隆,所以將其分離出來,并讓原來的對象保持獨(dú)立。隨后,它繼續(xù)做它希望的事情。甚至能返回指向這個新對象的一個句柄,而且不會對原來的對象產(chǎn)生任何副作用。注意下面這個多少有些古怪的語句:
v = (MyObject)v.clone();
它的作用正是創(chuàng)建一個本地副本。為避免被這樣的一個語句搞混淆,記住這種相當(dāng)奇怪的編碼形式在Java中是完全允許的,因?yàn)橛幸粋名字的所有東西實(shí)際都是一個句柄。所以句柄v用于克隆一個它所指向的副本,而且最終返回指向基礎(chǔ)類型Object的一個句柄(因?yàn)樗贠bject.clone()中是那樣被定義的),隨后必須將其造型為正確的類型。
在main()中,兩種不同參數(shù)傳遞方式的區(qū)別在于它們分別測試了一個不同的方法。輸出結(jié)果如下:
<code class="hljs" makefile="">a == ba = 12b = 12c != dc = 47d =48</code>
大家要記住這樣一個事實(shí):Java對“是否等價(jià)”的測試并不對所比較對象的內(nèi)部進(jìn)行檢查,從而核實(shí)它們的值是否相同。==和!=運(yùn)算符只是簡單地對比句柄的內(nèi)容。若句柄內(nèi)的地址相同,就認(rèn)為句柄指向同樣的對象,所以認(rèn)為它們是“等價(jià)”的。所以運(yùn)算符真正檢測的是“由于別名問題,句柄是否指向同一個對象?”
java Object.clone()的效果
調(diào)用Object.clone()時,實(shí)際發(fā)生的是什么事情呢?當(dāng)我們在自己的類里覆蓋clone()時,什么東西對于super.clone()來說是最關(guān)鍵的呢?根類中的clone()方法負(fù)責(zé)建立正確的存儲容量,并通過“按位復(fù)制”將二進(jìn)制位從原始對象中復(fù)制到新對象的存儲空間。也就是說,它并不只是預(yù)留存儲空間以及復(fù)制一個對象——實(shí)際需要調(diào)查出欲復(fù)制之對象的準(zhǔn)確大小,然后復(fù)制那個對象。由于所有這些工作都是在由根類定義之clone()方法的內(nèi)部代碼中進(jìn)行的(根類并不知道要從自己這里繼承出去什么),所以大家或許已經(jīng)猜到,這個過程需要用RTTI判斷欲克隆的對象的實(shí)際大小。采取這種方式,clone()方法便可建立起正確數(shù)量的存儲空間,并對那個類型進(jìn)行正確的按位復(fù)制。
不管我們要做什么,克隆過程的第一個部分通常都應(yīng)該是調(diào)用super.clone()。通過進(jìn)行一次準(zhǔn)確的復(fù)制,這樣做可為后續(xù)的克隆進(jìn)程建立起一個良好的基礎(chǔ)。隨后,可采取另一些必要的操作,以完成最終的克隆。
為確切了解其他操作是什么,首先要正確理解Object.clone()為我們帶來了什么。特別地,它會自動克隆所有句柄指向的目標(biāo)嗎?下面這個例子可完成這種形式的檢測:
<code class="hljs" java="">public class Snake implements Cloneable { private Snake next; private char c; 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); }}</code>
一條Snake(蛇)由數(shù)段構(gòu)成,每一段的類型都是Snake,
電腦資料
《java克隆對象clone()的用法和作用》(http://www.dameics.com)。所以,這是一個一段段鏈接起來的列表。所有段都是以循環(huán)方式創(chuàng)建的,每做好一段,都會使第一個構(gòu)建器參數(shù)的值遞減,直至最終為零。而為給每段賦予一個獨(dú)一無二的標(biāo)記,第二個參數(shù)(一個Char)的值在每次循環(huán)構(gòu)建器調(diào)用時都會遞增。increment()方法的作用是循環(huán)遞增每個標(biāo)記,使我們能看到發(fā)生的變化;而toString則循環(huán)打印出每個標(biāo)記。輸出如下:
s = :a:b:c:d:e
s2 = :a:b:c:d:e
after s.increment, s2 = :a:c:d:e:f
這意味著只有第一段才是由Object.clone()復(fù)制的,所以此時進(jìn)行的是一種“淺層復(fù)制”。若希望復(fù)制整條蛇——即進(jìn)行“深層復(fù)制”——必須在被覆蓋的clone()里采取附加的操作。
通?稍趶囊粋能克隆的類里調(diào)用super.clone(),以確保所有基礎(chǔ)類行動(包括Object.clone())能夠進(jìn)行。隨著是為對象內(nèi)每個句柄都明確調(diào)用一個clone();否則那些句柄會別名變成原始對象的句柄。構(gòu)建器的調(diào)用也大致相同——首先構(gòu)造基礎(chǔ)類,然后是下一個衍生的構(gòu)建器……以此類推,直到位于最深層的衍生構(gòu)建器。區(qū)別在于clone()并不是個構(gòu)建器,所以沒有辦法實(shí)現(xiàn)自動克隆。為了克隆,必須由自己明確進(jìn)行。
克隆合成對象
試圖深層復(fù)制合成對象時會遇到一個問題。必須假定成員對象中的clone()方法也能依次對自己的句柄進(jìn)行深層復(fù)制,以此類推。這使我們的操作變得復(fù)雜。為了能正常實(shí)現(xiàn)深層復(fù)制,必須對所有類中的代碼進(jìn)行控制,或者至少全面掌握深層復(fù)制中需要涉及的類,確保它們自己的深層復(fù)制能正確進(jìn)行。
下面這個例子總結(jié)了面對一個合成對象進(jìn)行深層復(fù)制時需要做哪些事情:
<code axapta="" class="hljs">class DepthReading implements Cloneable { private double depth; public DepthReading(double depth) { this.depth = depth; } public Object clone() { Object o = null; try { o = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return o; }}class TemperatureReading implements Cloneable { private long time; private double temperature; public TemperatureReading(double temperature) { time = System.currentTimeMillis(); this.temperature = temperature; } public Object clone() { Object o = null; try { o = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return o; }}class OceanReading implements Cloneable { private DepthReading depth; private TemperatureReading temperature; public OceanReading(double tdata, double ddata){ temperature = new TemperatureReading(tdata); depth = new DepthReading(ddata); } public Object clone() { OceanReading o = null; try { o = (OceanReading)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } o.depth = (DepthReading)o.depth.clone(); o.temperature = (TemperatureReading)o.temperature.clone(); return o; }}public class DeepCopy { public static void main(String[] args) { OceanReading reading = new OceanReading(33.9, 100.5); OceanReading r = (OceanReading)reading.clone(); }}</code>
DepthReading和TemperatureReading非常相似;它們都只包含了基本數(shù)據(jù)類型。所以clone()方法能夠非常簡單:調(diào)用super.clone()并返回結(jié)果即可。注意兩個類使用的clone()代碼是完全一致的。
OceanReading是由DepthReading和TemperatureReading對象合并而成的。為了對其進(jìn)行深層復(fù)制,clone()必須同時克隆OceanReading內(nèi)的句柄。為達(dá)到這個目標(biāo),super.clone()的結(jié)果必須造型成一個OceanReading對象(以便訪問depth和temperature句柄)。
java.lang.Object里面有一個方法clone()
protected Object clone()
throws CloneNotSupportedException創(chuàng)建并返回此對象的一個副本。“副本”的準(zhǔn)確含義可能依賴于對象的類。這樣做的目的是,對于任何對象 x,表達(dá)式:
x.clone() != x為 true,表達(dá)式:
x.clone().getClass() == x.getClass()也為 true,但這些并非必須要滿足的要求。一般情況下:
x.clone().equals(x)為 true,但這并非必須要滿足的要求。
按照慣例,返回的對象應(yīng)該通過調(diào)用 super.clone 獲得。如果一個類及其所有的超類(Object 除外)都遵守此約定,則 x.clone().getClass() == x.getClass()。
按照慣例,此方法返回的對象應(yīng)該獨(dú)立于該對象(正被復(fù)制的對象)。要獲得此獨(dú)立性,在 super.clone 返回對象之前,有必要對該對象的一個或多個字段進(jìn)行修改。這通常意味著要復(fù)制包含正在被復(fù)制對象的內(nèi)部“深層結(jié)構(gòu)”的所有可變對象,并使用對副本的引用替換對這些對象的引用。如果一個類只包含基本字段或?qū)Σ蛔儗ο蟮囊茫敲赐ǔ2恍枰薷?super.clone 返回的對象中的字段。
Object 類的 clone 方法執(zhí)行特定的復(fù)制操作。首先,如果此對象的類不能實(shí)現(xiàn)接口 Cloneable,則會拋出 CloneNotSupportedException。注意,所有的數(shù)組都被視為實(shí)現(xiàn)接口 Cloneable。否則,此方法會創(chuàng)建此對象的類的一個新實(shí)例,并像通過分配那樣,嚴(yán)格使用此對象相應(yīng)字段的內(nèi)容初始化該對象的所有字段;這些字段的內(nèi)容沒有被自我復(fù)制。所以,此方法執(zhí)行的是該對象的“淺表復(fù)制”,而不“深層復(fù)制”操作。
Object 類本身不實(shí)現(xiàn)接口 Cloneable,所以在類為 Object 的對象上調(diào)用 clone 方法將會導(dǎo)致在運(yùn)行時拋出異常。
返回:
此實(shí)例的一個副本。
拋出:
CloneNotSupportedException - 如果對象的類不支持 Cloneable 接口,則重寫 clone 方法的子類也會拋出此異常,以指示無法復(fù)制某個實(shí)例。
<code>其實(shí)clone()被定義為protected是有原因的,因?yàn)橛行⿻r候某些類實(shí)例我們不希望能被復(fù)制.那我們怎么用這個方法呢?只要使用一個super關(guān)鍵字就夠了.下面例子可以解釋清楚clone()的作用與其用法:</code>
<code avrasm="" class="hljs">public class C { static class Strings implements Cloneable { private String str; public void SetStr(String s){ this.str = s; } public String GetStr(){ return this.str; } public Object clone()throws CloneNotSupportedException{ return super.clone(); } } public static void main(String[] args) { try{ Strings str1 = new Strings(); str1.SetStr(Hello World!); System.out.println(-----------------); System.out.println(str1.SetStr(Hello World!);); System.out.println(str2 = (Strings)str1.clone();); Strings str2 = (Strings)str1.clone(); System.out.println(str1:+str1.GetStr()); System.out.println(str2:+str2.GetStr()); System.out.print(print object str1:); System.out.println(str1); System.out.print(print object str2:); System.out.println(str2); System.out.println(-----------------); System.out.println(str2.setStr(Hello!);); str2.SetStr(Hello!); System.out.println(str1:+str1.GetStr()); System.out.println(str2:+str2.GetStr()); System.out.print(print object str1:); System.out.println(str1); System.out.print(print object str2:); System.out.println(str2); System.out.println(-----------------); System.out.println(str1 = str2;); str1 = str2; System.out.print(print object str1:); System.out.println(str1); System.out.print(print object str2:); System.out.println(str2); System.out.println(-----------------); }catch(Exception ex){ System.out.println(ex.toString()); } }}</code>
運(yùn)行結(jié)果如下:
<code asciidoc="" class="hljs">-----------------str1.SetStr(Hello World!);str2 = (Strings)str1.clone();str1:Hello World!str2:Hello World!print object str1:C$Strings@de6cedprint object str2:C$Strings@c17164-----------------str2.setStr(Hello!);str1:Hello World!str2:Hello!print object str1:C$Strings@de6cedprint object str2:C$Strings@c17164-----------------str1 = str2;print object str1:C$Strings@c17164print object str2:C$Strings@c17164-----------------</code>