原型模式——Java中的克隆方法

寫在前面:

  • 你好,歡迎關注!
  • 我熱愛技術,熱愛分享,熱愛生活, 我始終相信:技術是開源的,知識是共享的!
  • 博客裏面的內容大部分均爲原創,是自己日常的學習記錄和總結,便於自己在後面的時間裏回顧,當然也是希望可以分享 自己的知識。如果你覺得還可以的話不妨關注一下,我們共同進步!
  • 個人除了分享博客之外,也喜歡看書,寫一點日常雜文和心情分享,如果你感興趣,也可以關注關注!
  • 公衆號:傲驕鹿先生

Java的所有類都是從java.lang.Object類繼承而來的,而Object類提供protected Object clone()方法對對象進行復制,子類當然也可以把這個方法置換掉,提供滿足自己需要的複製方法。對象的複製有一個基本問題,就是對象通常都有對其他的對象的引用。當使用Object類的clone()方法來複制一個對象時,此對象對其他對象的引用也同時會被複制一份

Java語言提供的Cloneable接口只起一個作用,就是在運行時期通知Java虛擬機可以安全地在這個類上使用clone()方法。通過調用這個clone()方法可以得到一個對象的複製。由於Object類本身並不實現Cloneable接口,因此如果所考慮的類沒有實現Cloneable接口時,調用clone()方法會拋出CloneNotSupportedException異常。

 
一、滿足克隆的條件

clone()方法將對象複製了一份並返還給調用者。所謂“複製”的含義與clone()方法是怎麼實現的。一般而言,clone()方法滿足以下的描述:

(1)對任何的對象x,都有:x.clone()!=x。換言之,克隆對象與原對象不是同一個對象。

(2)對任何的對象x,都有:x.clone().getClass() == x.getClass(),換言之,克隆對象與原對象的類型一樣。

(3)如果對象x的equals()方法定義其恰當的話,那麼x.clone().equals(x)應當成立的。

在JAVA語言的API中,凡是提供了clone()方法的類,都滿足上面的這些條件。JAVA語言的設計師在設計自己的clone()方法時,也應當遵守着三個條件。一般來說,上面的三個條件中的前兩個是必需的,而第三個是可選的。

二、深克隆和淺克隆

無論你是自己實現克隆方法,還是採用Java提供的克隆方法,都存在一個淺度克隆和深度克隆的問題。

  • 淺度克隆:只負責克隆按值傳遞的數據(比如基本數據類型、String類型),而不復制它所引用的對象,換言之,所有的對其他對象的引用都仍然指向原來的對象。

  • 深度克隆:除了淺度克隆要克隆的值外,還負責克隆引用類型的數據。那些引用其他對象的變量將指向被複制過的新對象,而不再是原有的那些被引用的對象。換言之,深度克隆把要複製的對象所引用的對象都複製了一遍,而這種對被引用到的對象的複製叫做間接複製。

深度克隆要深入到多少層,是一個不易確定的問題。在決定以深度克隆的方式複製一個對象的時候,必須決定對間接複製的對象時採取淺度克隆還是繼續採用深度克隆。因此,在採取深度克隆時,需要決定多深纔算深。此外,在深度克隆的過程中,很可能會出現循環引用的問題,必須小心處理。

三、利用序列化實現深度克隆

把對象寫到流裏的過程是序列化(Serialization)過程;而把對象從流中讀出來的過程則叫反序列化(Deserialization)過程。應當指出的是,寫到流裏的是對象的一個拷貝,而原對象仍然存在於JVM裏面。

在Java語言裏深度克隆一個對象,常常可以先使對象實現Serializable接口,然後把對象(實際上只是對象的拷貝)寫到一個流裏(序列化),再從流裏讀回來(反序列化),便可以重建對象。

public  Object deepClone() throws IOException, ClassNotFoundException{
    //將對象寫到流裏
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(this);
    //從流裏讀回來
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return ois.readObject();
}

這樣做的前提就是對象以及對象內部所有引用到的對象都是可序列化的,否則,就需要仔細考察那些不可序列化的對象可否設成transient,從而將之排除在複製過程之外。

淺度克隆顯然比深度克隆更容易實現,因爲Java語言的所有類都會繼承一個clone()方法,而這個clone()方法所做的正式淺度克隆。

有一些對象,比如線程(Thread)對象或Socket對象,是不能簡單複製或共享的。不管是使用淺度克隆還是深度克隆,只要涉及這樣的間接對象,就必須把間接對象設成transient而不予複製;或者由程序自行創建出相當的同種對象,權且當做複製件使用。

四、孫大聖的身外身法術

孫大聖的身外身本領如果在Java語言裏使用原型模式來實現的話,會怎麼樣呢?首先,齊天大聖(The Greatest Sage)即TheGreatestSage類扮演客戶角色。齊天大聖持有一個猢猻(Monkey)的實例,而猢猻就是大聖本尊。Monkey類具有繼承自java.lang.Object的clone()方法,因此,可以通過調用這個克隆方法來複制一個Monkey實例。

孫大聖本人用TheGreatestSage類代表:

public class TheGreatestSage {
    private Monkey monkey = new Monkey();
    
    public void change(){
        //克隆大聖本尊
        Monkey copyMonkey = (Monkey)monkey.clone();
        System.out.println("大聖本尊的生日是:" + monkey.getBirthDate());
        System.out.println("克隆的大聖的生日是:" + monkey.getBirthDate());
        System.out.println("大聖本尊跟克隆的大聖是否爲同一個對象 " + (monkey == copyMonkey));
        System.out.println("大聖本尊持有的金箍棒 跟 克隆的大聖持有的金箍棒是否爲同一個對象? " + (monkey.getStaff() == copyMonkey.getStaff()));
    }
    
    public static void main(String[]args){
        TheGreatestSage sage = new TheGreatestSage();
        sage.change();
    }
}

大聖本尊由Monkey類代表,這個類扮演具體原型角色:

public class Monkey implements Cloneable {
    //身高
    private int height;
    //體重
    private int weight;
    //生日
    private Date birthDate;
    //金箍棒
    private GoldRingedStaff staff;
    //構造函數
    public Monkey(){
        this.birthDate = new Date();
        this.staff = new GoldRingedStaff();
    }
    //克隆方法
    public Object clone(){
        Monkey temp = null;
        try {
            temp = (Monkey) super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            return temp;
        }
    }
    public int getHeight() {
        return height;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    public int getWeight() {
        return weight;
    }
    public void setWeight(int weight) {
        this.weight = weight;
    }
    public Date getBirthDate() {
        return birthDate;
    }
    public void setBirthDate(Date birthDate) {
        this.birthDate = birthDate;
    }
    public GoldRingedStaff getStaff() {
        return staff;
    }
    public void setStaff(GoldRingedStaff staff) {
        this.staff = staff;
    }

}

大聖還持有一個金箍棒的實例,金箍棒類GoldRingedStaff:

public class GoldRingedStaff {
    private float height = 100.0f;
    private float diameter = 10.0f;
    /**
     * 增長行爲,每次調用長度和半徑增加一倍
     */
    public void grow(){
        this.diameter *= 2;
        this.height *= 2;
    }
    /**
     * 縮小行爲,每次調用長度和半徑減少一半
     */
    public void shrink(){
        this.diameter /= 2;
        this.height /= 2;
    }
}

當運行TheGreatestSage類時,首先創建大聖本尊對象,而後淺度克隆大聖本尊對象。程序在運行時打印出的信息如下:

 

可以看出,首先,複製的大聖本尊具有和原始的大聖本尊對象一樣的birthDate,而本尊對象不相等,這表明他們二者是克隆關係;其次,複製的大聖本尊所持有的金箍棒和原始的大聖本尊所持有的金箍棒爲同一個對象。這表明二者所持有的金箍棒根本是一根,而不是兩根。

正如前面所述,繼承自java.lang.Object類的clone()方法是淺克隆。換言之,齊天大聖的所有化身所持有的金箍棒引用全都是指向一個對象的,這與《西遊記》中的描寫並不一致。要糾正這一點,就需要考慮使用深克隆。

爲做到深度克隆,所有需要複製的對象都需要實現java.io.Serializable接口。

孫大聖的源代碼:

public class TheGreatestSage {
    private Monkey monkey = new Monkey();
    
    public void change() throws IOException, ClassNotFoundException{
        Monkey copyMonkey = (Monkey)monkey.deepClone();
        System.out.println("大聖本尊的生日是:" + monkey.getBirthDate());
        System.out.println("克隆的大聖的生日是:" + monkey.getBirthDate());
        System.out.println("大聖本尊跟克隆的大聖是否爲同一個對象 " + (monkey == copyMonkey));
        System.out.println("大聖本尊持有的金箍棒 跟 克隆的大聖持有的金箍棒是否爲同一個對象? " + (monkey.getStaff() == copyMonkey.getStaff()));
    }
    
    public static void main(String[]args) throws IOException, ClassNotFoundException{
        TheGreatestSage sage = new TheGreatestSage();
        sage.change();
    }
}

在大聖本尊Monkey類裏面,有兩個克隆方法,一個是clone(),也即淺克隆;另一個是deepClone(),也即深克隆。在深克隆方法裏,大聖本尊對象(一個拷貝)被序列化,然後又被反序列化。反序列化的對象就成了一個深克隆的結果。

public class Monkey implements Cloneable,Serializable {
    //身高
    private int height;
    //體重
    private int weight;
    //生日
    private Date birthDate;
    //金箍棒
    private GoldRingedStaff staff;
    /**
     * 構造函數
     */
    public Monkey(){
        this.birthDate = new Date();
        staff = new GoldRingedStaff();
    }
    /**
     * 克隆方法
     */
    public Object clone(){
        Monkey temp = null;
        try {
            temp = (Monkey) super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            return temp;
        }
    }
    public  Object deepClone() throws IOException, ClassNotFoundException{
        //將對象寫到流裏
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        //從流裏讀回來
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return ois.readObject();
    }
    public int getHeight() {
        return height;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    public int getWeight() {
        return weight;
    }
    public void setWeight(int weight) {
        this.weight = weight;
    }
    public Date getBirthDate() {
        return birthDate;
    }
    public void setBirthDate(Date birthDate) {
        this.birthDate = birthDate;
    }
    public GoldRingedStaff getStaff() {
        return staff;
    }
    public void setStaff(GoldRingedStaff staff) {
        this.staff = staff;
    }
    
}

可以看到,大聖本尊持有一個金箍棒(GoldRingedStaff)的實例。在大聖複製件裏面,此金箍棒實例是原大聖本尊對象所持有的金箍棒對象的一個拷貝。在大聖本尊對象被序列化和反序列化時,它所持有的金箍棒對象也同時被序列化和反序列化,這使得複製的大聖的金箍棒和原大聖本尊對象所持有的金箍棒對象是兩個獨立的對象。

public class GoldRingedStaff implements Serializable{
    private float height = 100.0f;
    private float diameter = 10.0f;
    /**
     * 增長行爲,每次調用長度和半徑增加一倍
     */
    public void grow(){
        this.diameter *= 2;
        this.height *= 2;
    }
    /**
     * 縮小行爲,每次調用長度和半徑減少一半
     */
    public void shrink(){
        this.diameter /= 2;
        this.height /= 2;
    }
}

從運行的結果可以看出,大聖的金箍棒和他的身外之身的金箍棒是不同的對象。這是因爲使用了深克隆,從而把大聖本尊所引用的對象也都複製了一遍,其中也包括金箍棒。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章