clone方法

在實際編程過程中,我們常常要遇到這種情況:有一個對象A,在某一時刻A中已經包含了一些有效值,此時可能會需要一個和A完全相同新對象B,並且此後對B 任何改動都不會影響到A中的值,也就是說,A與B是兩個獨立的對象,但B的初始值是由A對象確定的。在Java語言中,用簡單的賦值語句是不能滿足這種需 求的。要滿足這種需求雖然有很多途徑,但實現clone()方法是其中最簡單,也是最高效的手段。 

Java的所有類都默認繼承java.lang.Object類,在java.lang.Object類中有一個方法clone()。JDK API的說明文檔解釋這個方法將返回Object對象的一個拷貝。要說明的有兩點:一是拷貝對象返回的是一個新對象,而不是一個引用。二是拷貝對象與用 new操作符返回的新對象的區別就是這個拷貝已經包含了一些原來對象的信息,而不是對象的初始信息。

3.2.怎樣應用clone()方法?

一個很典型的調用clone()代碼如下: 

class CloneClass implements Cloneable{ 
public int aInt; 
public Object clone(){ 
CloneClass o = null; 
try{ 
o = (CloneClass)super.clone(); 
}catch(CloneNotSupportedException e){ 
e.printStackTrace(); 

return o; 

} 
有三個值得注意的地方,

一是希望能實現clone功能的CloneClass類實現了Cloneable接口,這個接口屬於java.lang包, java.lang包已經被缺省的導入類中,所以不需要寫成java.lang.Cloneable。

另一個值得請注意的是重載了clone()方法。最 後在clone()方法中調用了super.clone(),這也意味着無論clone類的繼承結構是什麼樣的,super.clone()直接或間接調 用了java.lang.Object類的clone()方法。下面再詳細的解釋一下這幾點。

應該說第三點是最重要的,仔細觀察一下Object類的clone()一個native方法,native方法的效率一般來說都是遠高於java中的非 native方法。這也解釋了爲什麼要用Object中clone()方法而不是先new一個類,然後把原始對象中的信息賦到新對象中,雖然這也實現了 clone功能。對於第二點,也要觀察Object類中的clone()還是一個protected屬性的方法。這也意味着如果要應用clone()方 法,必須繼承Object類,在Java中所有的類是缺省繼承Object類的,也就不用關心這點了。然後重載clone()方法。還有一點要考慮的是爲 了讓其它類能調用這個clone類的clone()方法,重載之後要把clone()方法的屬性設置爲public。 、

那麼clone類爲什麼還要實現Cloneable接口呢?稍微注意一下,Cloneable接口是不包含任何方法的!其實這個接口僅僅是一個標誌,而且 這個標誌也僅僅是針對Object類中clone()方法的,如果clone類沒有實現Cloneable接口,並調用了Objectclone()法(也就是調用了super.Clone()方法),那麼Objectclone()方法就會拋出 CloneNotSupportedException異常。

以上是clone的最基本的步驟,想要完成一個成功的clone,還要了解什麼是"影子clone"和"深度clone"。

3.3什麼是影子clone?

下面的例子包含三個類UnCloneA,CloneB,CloneMain。CloneB類包含了一個UnCloneA的實例和一個int類型變量,並且 重載clone()方法。CloneMain類初始化UnCloneA類的一個實例b1,然後調用clone()方法生成了一個b1的拷貝b2。最後考察 一下b1和b2的輸出: 

package clone; 
class UnCloneA { 
private int i; 
public UnCloneA(int ii) { i = ii; } 
public void doublevalue() { i *= 2; } 
public String toString() { 
return Integer.toString(i); 


class CloneB implements Cloneable{ 
public int aInt; 
public UnCloneA unCA = new UnCloneA(111); 
public Object clone(){ 
CloneB o = null; 
try{ 
o = (CloneB)super.clone(); 
}catch(CloneNotSupportedException e){ 
e.printStackTrace(); 

return o; 


public class CloneMain { 
public static void main(String[] a){ 
CloneB b1 = new CloneB(); 
b1.aInt = 11; 
System.out.println("before clone,b1.aInt = "+ b1.aInt); 
System.out.println("before clone,b1.unCA = "+ b1.unCA); 

CloneB b2 = (CloneB)b1.clone(); 
b2.aInt = 22; 
b2.unCA.doublevalue(); 
System.out.println("================================="); 
System.out.println("after clone,b1.aInt = "+ b1.aInt); 
System.out.println("after clone,b1.unCA = "+ b1.unCA); 
System.out.println("================================="); 
System.out.println("after clone,b2.aInt = "+ b2.aInt); 
System.out.println("after clone,b2.unCA = "+ b2.unCA); 




/** RUN RESULT: 
before clone,b1.aInt = 11 
before clone,b1.unCA = 111 
================================= 
after clone,b1.aInt = 11 
after clone,b1.unCA = 222 
================================= 
after clone,b2.aInt = 22 
after clone,b2.unCA = 222 
*/ 

輸出的結果說明int類型的變量aInt和UnCloneA的實例對象unCA的clone結果不一致,int類型是真正的被clone了,因爲改變了 b2中的aInt變量,對b1的aInt沒有產生影響,也就是說,b2.aInt與b1.aInt已經佔據了不同的內存空間,b2.aInt是 b1.aInt的一個真正拷貝。相反,對b2.unCA的改變同時改變了b1.unCA,很明顯,b2.unCA和b1.unCA是僅僅指向同一個對象的 不同引用!從中可以看出,調用Object類中clone()方法產生的效果是:先在內存中開闢一塊和原始對象一樣的空間,然後原樣拷貝原始對象中的內 容。對基本數據類型,這樣的操作是沒有問題的,但對非基本類型變量,我們知道它們保存的僅僅是對象的引用,這也導致clone後的非基本類型變量和原始對 象中相應的變量指向的是同一個對象。 

大多時候,這種clone的結果往往不是我們所希望的結果,這種clone也被稱爲"影子clone"。要想讓b2.unCA指向與b2.unCA不同的對象,而且b2.unCA中還要包含b1.unCA中的信息作爲初始信息,就要實現深度clone。

 

3.4怎麼進行深度clone?

把上面的例子改成深度clone很簡單,需要兩個改變:一是讓UnCloneA類也實現和CloneB類一樣的clone功能(實現Cloneable接 口,重載clone()方法)。二是在CloneB的clone()方法中加入一句o.unCA = (UnCloneA)unCA.clone(); 

程序如下: 

package clone.ext; 
class UnCloneA implements Cloneable{ 
private int i; 
public UnCloneA(int ii) { i = ii; } 
public void doublevalue() { i *= 2; } 
public String toString() { 
return Integer.toString(i); 

public Object clone(){ 
UnCloneA o = null; 
try{ 
o = (UnCloneA)super.clone(); 
}catch(CloneNotSupportedException e){ 
e.printStackTrace(); 

return o; 


class CloneB implements Cloneable{ 
public int aInt; 
public UnCloneA unCA = new UnCloneA(111); 
public Object clone(){ 
CloneB o = null; 
try{ 
o = (CloneB)super.clone(); 
}catch(CloneNotSupportedException e){ 
e.printStackTrace(); 

o.unCA = (UnCloneA)unCA.clone(); 
return o; 


public class CloneMain { 
public static void main(String[] a){ 
CloneB b1 = new CloneB(); 
b1.aInt = 11; 
System.out.println("before clone,b1.aInt = "+ b1.aInt); 
System.out.println("before clone,b1.unCA = "+ b1.unCA); 

CloneB b2 = (CloneB)b1.clone(); 
b2.aInt = 22; 
b2.unCA.doublevalue(); 
System.out.println("================================="); 
System.out.println("after clone,b1.aInt = "+ b1.aInt); 
System.out.println("after clone,b1.unCA = "+ b1.unCA); 
System.out.println("================================="); 
System.out.println("after clone,b2.aInt = "+ b2.aInt); 
System.out.println("after clone,b2.unCA = "+ b2.unCA); 



/** RUN RESULT: 
before clone,b1.aInt = 11 
before clone,b1.unCA = 111 
================================= 
after clone,b1.aInt = 11 
after clone,b1.unCA = 111 
================================= 
after clone,b2.aInt = 22 
after clone,b2.unCA = 222 
*/ 

可以看出,現在b2.unCA的改變對b1.unCA沒有產生影響。此時b1.unCA與b2.unCA指向了兩個不同的UnCloneA實例,而且在 CloneB b2 = (CloneB)b1.clone();調用的那一刻b1和b2擁有相同的值,在這裏,b1.i = b2.i = 11。 

要知道不是所有的類都能實現深度clone的。例如,如果把上面的CloneB類中的UnCloneA類型變量改成StringBuffer類型,看一下 JDK API中關於StringBuffer的說明,StringBuffer沒有重載clone()方法,更爲嚴重的是StringBuffer還是一個 final類,這也是說我們也不能用繼承的辦法間接實現StringBuffer的clone。如果一個類中包含有StringBuffer類型對象或和 StringBuffer相似類的對象,我們有兩種選擇:要麼只能實現影子clone,要麼就在類的clone()方法中加一句(假設是 SringBuffer對象,而且變量名仍是unCA): o.unCA = new StringBuffer(unCA.toString()); //原來的是:o.unCA = (UnCloneA)unCA.clone(); 

還要知道的是除了基本數據類型能自動實現深度clone以外,String對象,Integer,Double等是一個例外,它clone後的表現好象也實現了深度clone,雖然這只是一個假象,但卻大大方便了我們的編程。 

Clone中String和StringBuffer的區別 

應該說明的是,這裏不是着重說明String和StringBuffer的區別,但從這個例子裏也能看出String類的一些與衆不同的地方。 

下面的例子中包括兩個類,CloneC類包含一個String類型變量和一個StringBuffer類型變量,並且實現了clone()方法。在 StrClone類中聲明瞭CloneC類型變量c1,然後調用c1的clone()方法生成c1的拷貝c2,在對c2中的String和 StringBuffer類型變量用相應的方法改動之後打印結果: 

package clone; 
class CloneC implements Cloneable{ 
public String str; 
public StringBuffer strBuff; 
public Object clone(){ 
CloneC o = null; 
try{ 
o = (CloneC)super.clone(); 
}catch(CloneNotSupportedException e){ 
e.printStackTrace(); 

return o; 



public class StrClone { 
public static void main(String[] a){ 
CloneC c1 = new CloneC(); 
c1.str = new String("initializeStr"); 
c1.strBuff = new StringBuffer("initializeStrBuff"); 
System.out.println("before clone,c1.str = "+ c1.str); 
System.out.println("before clone,c1.strBuff = "+ c1.strBuff); 

CloneC c2 = (CloneC)c1.clone(); 
c2.str = c2.str.substring(0,5); 
c2.strBuff = c2.strBuff.append(" change strBuff clone"); 
System.out.println("================================="); 
System.out.println("after clone,c1.str = "+ c1.str); 
System.out.println("after clone,c1.strBuff = "+ c1.strBuff); 
System.out.println("================================="); 
System.out.println("after clone,c2.str = "+ c2.str); 
System.out.println("after clone,c2.strBuff = "+ c2.strBuff); 


/* RUN RESULT 
before clone,c1.str = initializeStr 
before clone,c1.strBuff = initializeStrBuff 
================================= 
after clone,c1.str = initializeStr 
after clone,c1.strBuff = initializeStrBuff change strBuff clone 
================================= 
after clone,c2.str = initi 
after clone,c2.strBuff = initializeStrBuff change strBuff clone 

*/ 

打印的結果可以看出,String類型的變量好象已經實現了深度clone,因爲對c2.str的改動並沒有影響到c1.str!難道Java把 Sring類看成了基本數據類型?其實不然,這裏有一個小小的把戲,祕密就在於c2.str = c2.str.substring(0,5)這一語句!實質上,在clone的時候c1.str與c2.str仍然是引用,而且都指向了同一個 String對象。但在執行c2.str = c2.str.substring(0,5)的時候,它作用相當於生成了一個新的String類型,然後又賦回給c2.str。這是因爲String被Sun公司的工程師寫成了一個不可更改的類(immutable class),在所有String類中的函數都不能更改自身的值。下面給出很簡單的一個例子: 

package clone; public class StrTest { public static void main(String[] args) { String str1 = "This is a test for immutable"; String str2 = str1.substring(0,8); System.out.println("print str1 : " + str1); System.out.println("print str2 : " + str2); } } /* RUN RESULT print str1 : This is a test for immutable print str2 : This is */ 

例子中,雖然str1調用了substring()方法,但str1的值並沒有改變。類似的,String類中的其它方法也是如此。當然如果我們把最上面的例子中的這兩條語句 

c2.str = c2.str.substring(0,5); 
c2.strBuff = c2.strBuff.append(" change strBuff clone"); 

改成下面這樣: 

c2.str.substring(0,5); 
c2.strBuff.append(" change strBuff clone"); 

去掉了重新賦值的過程,c2.str也就不能有變化了,我們的把戲也就露餡了。但在編程過程中只調用 
c2.str.substring(0,5); 語句是沒有任何意義的。 

應該知道的是在Java中所有的基本數據類型都有一個相對應的類,象Integer類對應int類型,Double類對應double類型等等,這些類也 與String類相同,都是不可以改變的類。也就是說,這些的類中的所有方法都是不能改變其自身的值的。這也讓我們在編clone類的時候有了一個更多的 選擇。同時我們也可以把自己的類編成不可更改的類。

發佈了7 篇原創文章 · 獲贊 2 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章