Java克隆(Clone)的應用
 
 
簡介:
 
Java克隆(Clone)是Java語言的特性之一,但在實際中應用比較少見。但有時候用克隆會更方便更有效率。
 
對於克隆(Clone),Java有一些限制:
1、被克隆的類必須自己實現Cloneable 介面,以指示 Object.clone() 方法可以合法地對該類例項進行按欄位複製。Cloneable 介面實際上是個標識介面,沒有任何介面方法。
2、實現Cloneable介面的類應該使用公共方法重寫 Object.clone(它是受保護的)。某個物件實現了此介面就克隆它是不可能的。即使 clone 方法是反射性呼叫的,也無法保證它將獲得成功。
3、在Java.lang.Object類中克隆方法是這麼定義的:
protected Object clone()
                throws CloneNotSupportedException
建立並返回此物件的一個副本。表明是一個受保護的方法,同一個包中可見。
按照慣例,返回的物件應該通過呼叫 super.clone 獲得。
 
 
引題:
 
舉個例子說吧,現在有一個物件比如叫foo,你需要在建立當前物件的一個副本作為存根你能怎麼做?
 
假如你不用Clone,那麼你可以先new一個物件foo1:Foo foo1=new Foo(),然後用foo給foo1物件set值,這樣就得到foo的副本foo1;除此之外,別無選擇。
 
這樣說,也許有人會覺得說的過於絕對了,不過事實如此啊。
 
要產生一個副本,那副本要不要記憶體?—-當然要了,那就對了!既然需要記憶體,(不克隆的情況下)你不new還有什麼辦法呢?請大家時刻銘記物件是Java執行時產生的,駐留在計算機記憶體中。
 
常見錯誤:
下面我澄清幾個初學者容易犯迷糊的錯誤,同樣的問題,產生foo物件的副本:
1、Foo foo1=new Foo();
   foo1=foo;
   然後就想當然的認為副本foo1生成了!
 
錯誤原因:foo1沒錯是申請了記憶體,但是執行foo1=foo後,foo1就不在指向剛申請的記憶體區域了,轉而指向foo物件的記憶體區域,這時候,foo1、foo指向了同一記憶體區域。剛才new的操作製造一堆垃圾等著JVM回收。
 
2、Foo foo1=foo;
錯誤原因:還是兩個變數都指向了同一塊記憶體。
 
3、有些老鳥更厲害一些:在Foo中定義一個返回自身的方法:
    public Foo getInstance(){
        return this;
    }
 
    然後,Foo foo1=foo.getInstance();
 
錯誤原因:同上,主要還是沒有重新開闢記憶體,this在物件裡是什麼?—-就是物件自己的引用!那麼getInstance()自然返回的就是物件自己,反正又是兩個物件穿了一條褲子—-***,哈哈。錯得心服口服吧。為了節省篇幅,我在最後寫個例子,留給那些對此有異議的人看。
 
引入克隆
 
看了這麼多方法都不行,還很麻煩!乾脆用克隆吧,簡單明瞭。
 
廢話不說了,看例子:
定義兩個類CloneFooA、CloneFooB,然後寫個測試類CloneDemo分別克隆這兩個類的物件,然後列印測試結果到控制檯。
 
 
/**
 * Created by IntelliJ IDEA.
 * User: leizhimin
 * Date: 2007-9-20
 * Time: 19:40:44
 * 簡單類克隆實現
 * 要實現克隆,必須實現Cloneable介面,這是一個標識介面,沒有介面方法
 * 實現了 Cloneable 介面,以指示 Object.clone() 方法可以合法地對該類例項進行按欄位複製。
 * 按照慣例,實現此介面的類應該使用公共方法重寫 Object.clone(它是受保護的)。
 */
public class CloneFooA implements Cloneable {
    private String strA;
    private int intA;
 
    public CloneFooA(String strA, int intA) {
        this.strA = strA;
        this.intA = intA;
    }
 
    public String getStrA() {
        return strA;
    }
 
    public void setStrA(String strA) {
        this.strA = strA;
    }
 
    public int getIntA() {
        return intA;
    }
 
    public void setIntA(int intA) {
        this.intA = intA;
    }
 
    /**
     * @return 建立並返回此物件的一個副本。
     * @throws CloneNotSupportedException
     */
    public Object clone() throws CloneNotSupportedException {
        //直接呼叫父類的clone()方法,返回克隆副本
        return super.clone();
    }
}
 
 
/**
 * Created by IntelliJ IDEA.
 * User: leizhimin
 * Date: 2007-9-20
 * Time: 19:59:55
 * 深度克隆物件,當類存在聚合關係的時候,克隆就必須考慮聚合物件的克隆
 */
public class CloneFooB implements Cloneable {
    private CloneFooA fooA;
    private Double douB;
 
    public CloneFooB(Double douB) {
        this.douB = douB;
    }
 
    public CloneFooB(CloneFooA fooA, Double douB) {
        this.fooA = fooA;
        this.douB = douB;
    }
 
    public CloneFooA getFooA() {
        return fooA;
    }
 
    public void setFooA(CloneFooA fooA) {
        this.fooA = fooA;
    }
 
    public Double getDouB() {
        return douB;
    }
 
    public void setDouB(Double douB) {
        this.douB = douB;
    }
 
    /**
     * 克隆操作
     *
     * @return 自身物件的一個副本
     * @throws CloneNotSupportedException
     */
    public Object clone() throws CloneNotSupportedException {
        //先呼叫父類的克隆方法進行克隆操作
        CloneFooB cloneFooB = (CloneFooB) super.clone();
        //對於克隆後出的物件cloneFooB,如果其成員fooA為null,則不能呼叫clone(),否則出空指標異常
        if (this.fooA != null)
            cloneFooB.fooA = (CloneFooA) this.fooA.clone();
 
        return cloneFooB;
    }
}
 
 
/**
 * Created by IntelliJ IDEA.
 * User: leizhimin
 * Date: 2007-9-20
 * Time: 19:52:01
 * 測試類:分別克隆CloneFooA和CloneFooB類,並列印克隆前後的結果.
 */

public class CloneDemo {
    public static void main(String args[]) throws CloneNotSupportedException {
        //CloneFooA克隆前
        CloneFooA fooA1 = new CloneFooA(“FooA”, 11);
        System.out.println(“CloneFooA的物件克隆前物件fooA1值為: ” + fooA1.getStrA() + “,” + fooA1.getIntA());
        //CloneFooA克隆後
        CloneFooA fooA2 = (CloneFooA) fooA1.clone();
        System.out.println(“CloneFooA的物件克隆後物件fooA2值為: ” + fooA2.getStrA() + “,” + fooA2.getIntA());
        //比較fooA1和fooA2記憶體地址
        if (fooA1 == fooA2) System.out.println(“比較fooA1和fooA2記憶體地址:相等!”);
        else System.out.println(“比較fooA1和fooA2記憶體地址:不相等!”);
 
        System.out.println(“————————-“);
 
        //CloneFooB克隆前
        CloneFooB fooB1 = new CloneFooB(fooA1, new Double(“33”));
        System.out.println(“CloneFooB的物件克隆前物件fooB1值為: ” + fooB1.getFooA().getStrA() + “,” + fooB1.getFooA().getIntA() + ” | ” + fooB1.getDouB());
        //CloneFooB克隆後
        CloneFooB fooB2 = (CloneFooB) fooB1.clone();
        System.out.println(“CloneFooB的物件克隆前物件fooB2值為: ” + fooB2.getFooA().getStrA() + “,” + fooB2.getFooA().getIntA() + ” | ” + fooB2.getDouB());
 
        if (fooA1 == fooA2) System.out.println(“比較fooB1和fooB2記憶體地址:相等!”);
        else System.out.println(“比較fooB1和fooB2記憶體地址:不相等!”);
    }
}
 
執行結果:
 
CloneFooA的物件克隆前物件fooA1值為: FooA,11
CloneFooA的物件克隆後物件fooA2值為: FooA,11
比較fooA1和fooA2記憶體地址:不相等!
————————-
CloneFooB的物件克隆前物件fooB1值為: FooA,11 | 33.0
CloneFooB的物件克隆前物件fooB2值為: FooA,11 | 33.0
比較fooB1和fooB2記憶體地址:不相等!
 
Process finished with exit code 0
 
 
反面教材:
 
最後,我給出我上面提出到最後要給出的反面例子。
 
隨便寫一個,在CloneFooA 的基礎上做了少許改動,內容如下:
 
public class CloneFooA implements Cloneable {
    private String strA;
    private int intA;
 
    public CloneFooA(String strA, int intA) {
        this.strA = strA;
        this.intA = intA;
    }
 
    public String getStrA() {
        return strA;
    }
 
    public void setStrA(String strA) {
        this.strA = strA;
    }
 
    public int getIntA() {
        return intA;
    }
 
    public void setIntA(int intA) {
        this.intA = intA;
    }
 
    /**
     * @return 建立並返回此物件的一個副本。
     * @throws CloneNotSupportedException
     */
    public Object clone() throws CloneNotSupportedException {
        //直接呼叫父類的clone()方法,返回克隆副本
        return super.clone();
    }
 
    /**
     * @return 返回執行時的物件
     */
    public CloneFooA getInstance(){
        return this;
    }
 
    public static void main(String args[]){
        CloneFooA fooA=new CloneFooA(“aa”,11);
        System.out.println(fooA.getStrA()+”  “+fooA.getIntA());
 
        CloneFooA fooA1=fooA.getInstance();
        System.out.println(fooA1.getStrA()+”  “+fooA1.getIntA());
        if(fooA==fooA1) System.out.println(“fooA和fooA1記憶體地址相等!”);
 
        System.out.println(“————————-“);
 
        //改變後fooA或者fooA1中任何一個,看看另外一個是否會改變
        fooA1.setStrA(“bb”);
        System.out.println(fooA.getStrA()+”  “+fooA.getIntA());
        System.out.println(fooA1.getStrA()+”  “+fooA1.getIntA());
 
        if(fooA==fooA1) System.out.println(“fooA和fooA1記憶體地址相等,改變fooA1後,fooA的值也跟著變化了”);
    }
}
 
執行結果:
 
aa  11
aa  11
fooA和fooA1記憶體地址相等!
————————-
bb  11
bb  11
fooA和fooA1記憶體地址相等,改變fooA1後,fooA的值也跟著變化了
 
Process finished with exit code 0