內容摘要
若需修改一個對象,同時不想改變調用者的對象,就要制作該對象的一個本地副本,java克隆對象clone()的用法和作用。這也是本地副本最常見的一種用途。若決定制作一個本地副本,只需簡單地使用clone()方法即可。Clone是“克隆”的意思,即制作完全一模一樣的副本。這個方法在基礎類Object中定義成“protected”(受保護)模式。但在希望克隆的任何衍生類中,必須將其覆蓋為“public”模式。例如,標準庫類Vector覆蓋了clone(),所以能為Vector調用clone(),
<code class="hljs" php="">寫clone()方法時,通常都有一行代碼 super.clone();clone 有缺省行為,super.clone();因為首先要把父類中的成員復制到位,然后才是復制自己的成員。</code>java克隆對象若需修改一個對象,同時不想改變調用者的對象,就要制作該對象的一個本地副本。這也是本地副本最常見的一種用途。若決定制作一個本地副本,只需簡單地使用clone()方法即可。Clone是“克隆”的意思,即制作完全一模一樣的副本。這個方法在基礎類Object中定義成“protected”(受保護)模式。但在希望克隆的任何衍生類中,必須將其覆蓋為“public”模式。例如,標準庫類Vector覆蓋了clone(),所以能為Vector調用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()方法產生了一個Object,后者必須立即重新造型為正確類型。這個例子指出Vector的clone()方法不能自動嘗試克隆Vector內包含的每個對象——由于別名問題,老的Vector和克隆的Vector都包含了相同的對象。我們通常把這種情況叫作“簡單復制”或者“淺層復制”,因為它只復制了一個對象的“表面”部分。實際對象除包含這個“表面”以外,還包括句柄指向的所有對象,以及那些對象又指向的其他所有對象,由此類推。這便是“對象網”或“對象關系網”的由來。若能復制下所有這張網,便叫作“全面復制”或者“深層復制”。
在輸出中可看到淺層復制的結果,注意對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中定義的,但克隆仍然不會在每個類里自動進行。這似乎有些不可思議,因為基礎類方法在衍生類里是肯定能用的。但Java確實有點兒反其道而行之;如果想在一個類里使用克隆方法,唯一的辦法就是專門添加一些代碼,以便保證克隆的正常進行。
使用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不可能在衍生之后反而縮小方法的訪問范圍。換言之,一旦對象變得可以克隆,從它衍生的任何東西都是能夠克隆的,除非使用特殊的機制(后面討論)令其“關閉”克隆能力。
實現Cloneable接口
為使一個對象的克隆能力功成圓滿,還需要做另一件事情:實現Cloneable接口。這個接口使人稍覺奇怪,因為它是空的!
interface Cloneable {}
之所以要實現這個空接口,顯然不是因為我們準備上溯造型成一個Cloneable,以及調用它的某個方法。有些人認為在這里使用接口屬于一種“欺騙”行為,因為它使用的特性打的是別的主意,而非原來的意思。Cloneable interface的實現扮演了一個標記的角色,封裝到類的類型中。
兩方面的原因促成了Cloneable interface的存在。首先,可能有一個上溯造型句柄指向一個基礎類型,而且不知道它是否真的能克隆那個對象。在這種情況下,可用instanceof關鍵字(第11章有介紹)調查句柄是否確實同一個能克隆的對象連接:
if(myHandle instanceof Cloneable) // …
第二個原因是考慮到我們可能不愿所有對象類型都能克隆。所以Object.clone()會驗證一個類是否真的是實現了Cloneable接口。若答案是否定的,則“擲”出一個CloneNotSupportedException違例。所以在一般情況下,我們必須將“implement Cloneable”作為對克隆能力提供支持的一部分。
java的clone實現理解了實現clone()方法背后的所有細節后,便可創建出能方便復制的類,以便提供了一個本地副本:
<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()必須能夠訪問,所以必須將其設為public(公共的)。其次,作為clone()的初期行動,應調用clone()的基礎類版本。這里調用的clone()是Object內部預先定義好的。之所以能調用它,是由于它具有protected(受到保護的)屬性,所以能在衍生的類里訪問。
Object.clone()會檢查原先的對象有多大,再為新對象騰出足夠多的內存,將所有二進制位從原來的對象復制到新對象。這叫作“按位復制”,而且按一般的想法,這個工作應該是由clone()方法來做的。但在Object.clone()正式開始操作前,首先會檢查一個類是否Cloneable,即是否具有克隆能力——換言之,它是否實現了Cloneable接口。若未實現,Object.clone()就擲出一個CloneNotSupportedException違例,指出我們不能克隆它。因此,我們最好用一個try-catch塊將對super.clone()的調用代碼包圍(或封裝)起來,試圖捕獲一個應當永不出現的違例(因為這里確實已實現了Cloneable接口)。
在LocalCopy中,兩個方法g()和f()揭示出兩種參數傳遞方法間的差異。其中,g()演示的是按引用傳遞,它會修改外部對象,并返回對那個外部對象的一個引用。而f()是對自變量進行克隆,所以將其分離出來,并讓原來的對象保持獨立。隨后,它繼續做它希望的事情。甚至能返回指向這個新對象的一個句柄,而且不會對原來的對象產生任何副作用。注意下面這個多少有些古怪的語句:
v = (MyObject)v.clone();
它的作用正是創建一個本地副本。為避免被這樣的一個語句搞混淆,記住這種相當奇怪的編碼形式在Java中是完全允許的,因為有一個名字的所有東西實際都是一個句柄。所以句柄v用于克隆一個它所指向的副本,而且最終返回指向基礎類型Object的一個句柄(因為它在Object.clone()中是那樣被定義的),隨后必須將其造型為正確的類型。
在main()中,兩種不同參數傳遞方式的區別在于它們分別測試了一個不同的方法。輸出結果如下:
<code class="hljs" makefile="">a == ba = 12b = 12c != dc = 47d =48</code>大家要記住這樣一個事實:Java對“是否等價”的測試并不對所比較對象的內部進行檢查,從而核實它們的值是否相同。==和!=運算符只是簡單地對比句柄的內容。若句柄內的地址相同,就認為句柄指向同樣的對象,所以認為它們是“等價”的。所以運算符真正檢測的是“由于別名問題,句柄是否指向同一個對象?”
java Object.clone()的效果調用Object.clone()時,實際發生的是什么事情呢?當我們在自己的類里覆蓋clone()時,什么東西對于super.clone()來說是最關鍵的呢?根類中的clone()方法負責建立正確的存儲容量,并通過“按位復制”將二進制位從原始對象中復制到新對象的存儲空間。也就是說,它并不只是預留存儲空間以及復制一個對象——實際需要調查出欲復制之對象的準確大小,然后復制那個對象。由于所有這些工作都是在由根類定義之clone()方法的內部代碼中進行的(根類并不知道要從自己這里繼承出去什么),所以大家或許已經猜到,這個過程需要用RTTI判斷欲克隆的對象的實際大小。采取這種方式,clone()方法便可建立起正確數量的存儲空間,并對那個類型進行正確的按位復制。
不管我們要做什么,克隆過程的第一個部分通常都應該是調用super.clone()。通過進行一次準確的復制,這樣做可為后續的克隆進程建立起一個良好的基礎。隨后,可采取另一些必要的操作,以完成最終的克隆。
為確切了解其他操作是什么,首先要正確理解Object.clone()為我們帶來了什么。特別地,它會自動克隆所有句柄指向的目標嗎?下面這個例子可完成這種形式的檢測:
<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(蛇)由數段構成,每一段的類型都是Snake,電腦資料《java克隆對象clone()的用法和作用》(https://www.unjs.com)。所以,這是一個一段段鏈接起來的列表。所有段都是以循環方式創建的,每做好一段,都會使第一個構建器參數的值遞減,直至最終為零。而為給每段賦予一個獨一無二的標記,第二個參數(一個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()并不是個構建器,所以沒有辦法實現自動克隆。為了克隆,必須由自己明確進行。
克隆合成對象試圖深層復制合成對象時會遇到一個問題。必須假定成員對象中的clone()方法也能依次對自己的句柄進行深層復制,以此類推。這使我們的操作變得復雜。為了能正常實現深層復制,必須對所有類中的代碼進行控制,或者至少全面掌握深層復制中需要涉及的類,確保它們自己的深層復制能正確進行。
下面這個例子總結了面對一個合成對象進行深層復制時需要做哪些事情:
<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非常相似;它們都只包含了基本數據類型。所以clone()方法能夠非常簡單:調用super.clone()并返回結果即可。注意兩個類使用的clone()代碼是完全一致的。
OceanReading是由DepthReading和TemperatureReading對象合并而成的。為了對其進行深層復制,clone()必須同時克隆OceanReading內的句柄。為達到這個目標,super.clone()的結果必須造型成一個OceanReading對象(以便訪問depth和temperature句柄)。
java.lang.Object里面有一個方法clone()
protected Object clone()
throws CloneNotSupportedException創建并返回此對象的一個副本。“副本”的準確含義可能依賴于對象的類。這樣做的目的是,對于任何對象 x,表達式:
x.clone() != x為 true,表達式:
x.clone().getClass() == x.getClass()也為 true,但這些并非必須要滿足的要求。一般情況下:
x.clone().equals(x)為 true,但這并非必須要滿足的要求。
按照慣例,返回的對象應該通過調用 super.clone 獲得。如果一個類及其所有的超類(Object 除外)都遵守此約定,則 x.clone().getClass() == x.getClass()。
按照慣例,此方法返回的對象應該獨立于該對象(正被復制的對象)。要獲得此獨立性,在 super.clone 返回對象之前,有必要對該對象的一個或多個字段進行修改。這通常意味著要復制包含正在被復制對象的內部“深層結構”的所有可變對象,并使用對副本的引用替換對這些對象的引用。如果一個類只包含基本字段或對不變對象的引用,那么通常不需要修改 super.clone 返回的對象中的字段。
Object 類的 clone 方法執行特定的復制操作。首先,如果此對象的類不能實現接口 Cloneable,則會拋出 CloneNotSupportedException。注意,所有的數組都被視為實現接口 Cloneable。否則,此方法會創建此對象的類的一個新實例,并像通過分配那樣,嚴格使用此對象相應字段的內容初始化該對象的所有字段;這些字段的內容沒有被自我復制。所以,此方法執行的是該對象的“淺表復制”,而不“深層復制”操作。
Object 類本身不實現接口 Cloneable,所以在類為 Object 的對象上調用 clone 方法將會導致在運行時拋出異常。
返回:
此實例的一個副本。
拋出:
CloneNotSupportedException - 如果對象的類不支持 Cloneable 接口,則重寫 clone 方法的子類也會拋出此異常,以指示無法復制某個實例。
<code>其實clone()被定義為protected是有原因的,因為有些時候某些類實例我們不希望能被復制.那我們怎么用這個方法呢?只要使用一個super關鍵字就夠了.下面例子可以解釋清楚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>運行結果如下:
<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> 这里有更多你想看的
|