我們在編碼過程經(jīng)常會碰到將一個對象傳遞給另一個對象,java中對于基本型變量
采用的是值傳遞,而對于對象比如bean傳遞時采用的是應(yīng)用傳遞也就是地址傳遞,
而很多時候?qū)τ趯ο髠鬟f我們也希望能夠象值傳遞一樣,使得傳遞之前和之后有
不同的內(nèi)存地址,在這種情況下我們一般采用以下兩種情況。
 
1 對象克隆

什么是"clone"?

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

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

怎樣應(yīng)用clone()方法?

一個很典型的調(diào)用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;
}

有三個值得注意的地方,一是希望能實現(xiàn)clone功能的CloneClass類實現(xiàn)了Cloneable接口,這個接口屬于java.lang包, java.lang包已經(jīng)被缺省的導(dǎo)入類中,所以不需要寫成java.lang.Cloneable。另一個值得請注意的是重載了clone()方法。最 后在clone()方法中調(diào)用了super.clone(),這也意味著無論clone類的繼承結(jié)構(gòu)是什么樣的,super.clone()直接或間接調(diào) 用了java.lang.Object類的clone()方法。下面再詳細的解釋一下這幾點。

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

那么clone類為什么還要實現(xiàn)Cloneable接口呢?稍微注意一下,Cloneable接口是不包含任何方法的!其實這個接口僅僅 是一個標(biāo)志,而且這個標(biāo)志也僅僅是針對Object類中clone()方法的,如果clone類沒有實現(xiàn)Cloneable接口,并調(diào)用了Object的 clone()方法(也就是調(diào)用了super.Clone()方法),那么Object的clone()方法就會拋出 CloneNotSupportedException異常。

什么是影子clone?

下面的例子包含三個類UnCloneA,CloneB,CloneMain。CloneB類包含了一個UnCloneA的實例和一個int 類型變量,并且重載clone()方法。CloneMain類初始化UnCloneA類的一個實例b1,然后調(diào)用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
*/

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

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

怎么進行深度clone?

把上面的例子改成深度clone很簡單,需要兩個改變:一是讓UnCloneA類也實現(xiàn)和CloneB類一樣的clone功能(實現(xiàn) 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
*/

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

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

還要知道的是除了基本數(shù)據(jù)類型能自動實現(xiàn)深度clone以外,String對象是一個例外,它clone后的表現(xiàn)好象也實現(xiàn)了深度clone,雖然這只是一個假象,但卻大大方便了我們的編程。

通過以上我們可以看出在某些情況下,我們可以利用clone方法來實現(xiàn)對象只見的復(fù)制,但對 于比較復(fù)雜的對象(比如對象中包含其他對象,其他對象又包含別的對象.....)這樣我們必須進行層層深度clone,每個對象需要實現(xiàn) cloneable接口,比較麻煩,那就繼續(xù)學(xué)習(xí)下一個序列化方法。

2 對象序列化

所謂對象序列化就是將對象的狀態(tài)轉(zhuǎn)換成字節(jié)流,以后可以通過這些值再生成相同狀態(tài)的對象。這個過程也可以通過網(wǎng)絡(luò)實現(xiàn),可以先在Windows機器上創(chuàng)建一個對象,對其序列化,然后通過網(wǎng)絡(luò)發(fā)給一臺Unix機器,然后在那里準(zhǔn)確無誤地重新“裝配”。是不是很神奇。

也許你會說,只了解一點點,但從來沒有接觸過,其實未必如此。RMI、Socket、JMS、EJB你總該用過一種吧,彼此為什么能夠傳遞Java對象,當(dāng)然都是對象序列化機制的功勞。

第一次使用Java的對象序列化是做某項目,當(dāng)時要求把幾棵非常復(fù)雜的樹(JTree)及相應(yīng)的數(shù)據(jù)保存下來(就是我們常用的保存功能),以便下次運行程序時可以繼續(xù)上次的操作。

那時XML技術(shù)在網(wǎng)上非常的熱,而且功能也強大,再加上樹的結(jié)構(gòu)本來就和XML存儲數(shù)據(jù)的格式很像。作為一項對新技術(shù)比較有興趣的我當(dāng)然很 想嘗試一下。不過經(jīng)過仔細分析,發(fā)現(xiàn)如果采用XML保存數(shù)據(jù),后果真是難以想象:哪棵樹的哪個節(jié)點被展開、展開到第幾級、節(jié)點當(dāng)前的屬性是什么。真是不知 該用A、B、C還是用1、2、3來表示。

還好,發(fā)現(xiàn)了Java的對象序列化機制,問題迎刃而解,只需簡單的將每棵樹的根節(jié)點序列化保存到硬盤上,下次再通過反序列化后的根節(jié)點就可以輕松的構(gòu)造出和原來一模一樣的樹來。

其實保存數(shù)據(jù),尤其是復(fù)雜數(shù)據(jù)的保存正是對象序列化的典型應(yīng)用。最近另一個項目就遇到了需要對非常復(fù)雜的數(shù)據(jù)進行存取,通過使用對象的序列化,問題同樣化難為簡。

對象的序列化還有另一個容易被大家忽略的功能就是對象復(fù)制(Clone),Java中通過Clone機制可以復(fù)制大部分的對象,但是眾所周 知,Clone有深層Clone和淺層Clone,如果你的對象非常非常復(fù)雜,假設(shè)有個100層的Collection(夸張了點),如果你想實現(xiàn)深層 Clone,真是不敢想象,如果使用序列化,不會超過10行代碼就可以解決。

還有就是Swing組件,如果你有兩個很象很象(或是一模一樣)的比較難以構(gòu)造的Swing組件,你該怎么辦,也許你想到了Clone,但是偏偏Java的Swing組件沒有提供Clone方法。別急,使用序列化,6行代碼搞定:

ByteArrayOutputStream
byteOut = new ByteArrayOutputStream(); 
ObjectOutputStream out
= new ObjectOutputStream(byteOut); 
out.writeObject(combo); 

ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); 
ObjectInputStream in
=new ObjectInputStream(byteIn); 
JComboBox comb2 = (JComboBox)in.readObject();


雖然Java的序列化非常簡單、強大,但是要用好,還有很多地方需要注意。比如曾經(jīng)序列化了一個對象,可由于某種原因,該類做了一點點改動,然后重新被編譯,那么這時反序列化剛才的對象,將會出現(xiàn)異常。

你可以通過添加serialVersionUID屬性來解決這個問題。如果你的類是個單態(tài)(Singleton)類,是否允許用戶通過序列化機制復(fù)制該類,如果不允許你需要謹(jǐn)慎對待該類的實現(xiàn)。 

    /**
     * Clone Object
     * @param obj
     * @return
     * @throws Exception
     */
    private Object cloneObject(Object obj) throws Exception{
        ByteArrayOutputStream  byteOut = new ByteArrayOutputStream(); 
        ObjectOutputStream out = new ObjectOutputStream(byteOut); 
        out.writeObject(obj);
       
        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); 
        ObjectInputStream in =new ObjectInputStream(byteIn);
       
        return in.readObject();
    }

參考資料:

1.http://www-128.ibm.com/developerworks/cn/java/l-jpointer/index.html

2.http://www.softexam.cn/eschool/details.asp?id=10930