克隆羊多利之原型模式

今天就不想開頭了,直接來吧。如果想要創建幾個和某對象一模一樣的新對象,我們很容易想到new對象,在構造器裏面進行復制即可。但是今天就看一個新模式,原型模式。

創建對象的正常方法(蠢方法)

背景

新建一個Sheep類,並在客戶端Client裏面創建一個sheep對象,那我們還想要多添加幾個克隆羊,即和sheep對象一模一樣的幾個對象。我們很容易想到的是,直接new方法,再通過構造方法,將sheep裏面的幾個參數進行賦值,具體代碼如下。

代碼

Sheep類:

public class Sheep   {
    private String name;
    private int age;
    private String color;

    public Sheep(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }

}

Client類:

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Sheep sheep = new Sheep("tom", 1, "白色");

        Sheep sheep1=new Sheep(sheep.getName(),sheep.getAge(),sheep.getColor());
        Sheep sheep2=new Sheep(sheep.getName(),sheep.getAge(),sheep.getColor());

        System.out.println(sheep1.toString());
        System.out.println(sheep2.toString());
        System.out.println(sheep.hashCode()+","+sheep1.hashCode()+","+sheep2.hashCode());
    }
}

運行結果:

優點

比較好理解,簡單易操作,傻白甜操作。

缺點

1.在創建新的對象時,總是需要獲取原始對象的值,如果創建的對象比較複雜,效率較低。

2.總是需要重新初始化對象,而不是動態的獲取對象運行時的狀態,不夠靈活

引入原型模式

官方概念

用原型實例(原來的對象)來指定創建對象的種類,並且通過拷貝這些原型,創建新的對象。

白話文

就是通過一箇舊對象,創建一個新對象,即克隆羊多利。打個比方,咱能單個複製多利身上的部位,然後拼起來,但是這種方法麻煩且低效,部位的增加可能導致越來越低能。我們也能通過某種方法,直接複製所有。某種方法映射到設計模式裏面就是原型模式。

創建對象的新方法(clone)

方法概念描述

原型類需要具備以下兩個條件

  • 實現Cloneable接口。在java語言有一個Cloneable接口,它的作用只有一個,就是在運行時通知虛擬機可以安全地在實現了此接口的類上使用clone方法。
  • 重寫Object類中的clone方法。Java中,所有類的父類都是Object類,Object類中有一個clone方法,作用是返回對象的一個拷貝,但是其作用域protected類型的,一般的類無法調用,因此,原型類需要將clone方法的作用域修改爲public類型。

代碼

Sheep類:(與之前的Sheep區別就是實現了Cloneable接口及重寫了clone方法)

 

public class Sheep implements Cloneable   {
    private String name;
    private int age;
    private String color;

    public Sheep(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Sheep otherSheep=(Sheep)super.clone();
        return otherSheep;
    }
}

Client類:

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Sheep sheep = new Sheep("tom", 1, "白色");

        Sheep sheep1 = (Sheep) sheep.clone();
        System.out.println(sheep.toString());
        System.out.println(sheep1.toString());
        System.out.println(sheep.hashCode()+","+sheep1.hashCode());
    }
}

 

運行結果:

插曲(爲什麼要實現Cloneable接口,可不可以不實現?)

在上面的方法描述中我們可以知道了要實現Cloneable接口,但是我們看看如果不實現,有什麼問題?

Sheep類:

public class Sheep{
    private String name;
    private int age;
    private String color;

   //構造器,setter/getter,toString就先略去了,看重點

    @Override
    protected Object clone() {
        Sheep otherSheep=null;
        try{
             otherSheep=(Sheep)super.clone();
            return otherSheep;
        }catch(Exception e){
            e.printStackTrace();
        }
        return  otherSheep;
    }
}

test類一樣,我就不貼了,直接來看運行結果:

我們可以看到直接拋出了異常,這是因爲如果對象的類不支持Cloneable接口,重寫clone方法的子類也可以引發此異常以指示無法克隆實例。所以啊,都要寫,不要偷懶。

淺拷貝

舉個例子,如果羊對象裏面有個house這種的複雜對象,我們看能不能拷貝成功。

Sheep類:

 

public class Sheep implements Cloneable{
    private String name;
    private int age;
    private String color;
    private House house;

    public Sheep(String name, int age, String color,House house) {
        this.name = name;
        this.age = age;
        this.color = color;
        this.house=house;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public House getHouse() {
        return house;
    }

    public void setHouse(House house) {
        this.house = house;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                ", house=" + house +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
       return super.clone();
    }
}

House類:

 

public class House implements Cloneable {
    private String name;
    private String address;

    public House(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Client類:

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        House house=new House("羊羊家","地球上");
        Sheep sheep = new Sheep("tom", 1, "白色",house);

        Sheep sheep1 = (Sheep) sheep.clone();

        System.out.println(sheep.getHouse().hashCode()+","+sheep1.getHouse().hashCode());
    }
}

 

運行結果:

我們可以看到兩個Sheep對象中house的hashCode並沒有變化,說明他們實際上指向的是同一片空間,即爲淺拷貝,那我們之前的例子都是淺拷貝,因爲Sheep對象中的各個屬性的hashCode是一樣的,他們指向的是同一片內存空間。那我們應該如何讓裏面對象的值也不一樣呢?那就是下面的深拷貝。

深拷貝(複雜對象)

方法一(使用自己子對象的clone方法)

Sheep類:(與之前的區別只是方法上的區別,所以就不貼重複代碼啦)

  @Override
    protected Object clone() throws CloneNotSupportedException {
        Sheep otherSheep=(Sheep) super.clone();

        otherSheep.setHouse((House)house.clone());
        return  otherSheep;
    }

 

方法二(序列化和反序列化)

 

public class Sheep implements Serializable, Cloneable {
    private String name;
    private int age;
    private String color;
    private House house;

 //setter/getter方法,構造器,toString方法都略去了

    public Object clone2() {
        Sheep sheep = null;

        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;
        try {
            //序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            sheep = (Sheep) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //關閉流
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return sheep;
    }
} 

 

 public class House implements Serializable,Cloneable {
    private String name;
    private String address;

  //setter/getter方法,構造器,toString方法都略去了
}

 

優點

在內存中二進制流的拷貝,比直接new一個對象的性能要好,畢竟人家是native方法,可以直接操作內存,是親兒子。

缺點

沒錯,親兒子也有缺點。當處理簡單類的時候,他的性能還不如new,但當處理複雜類的時候,性能就比new高出一大截啦。

咱來試試,普通的House類,有兩個基本類型的參數,分別是name和address,我們看new和利用原型模式分別用了多少毫秒?

 

public class House implements Serializable,Cloneable {
    private String name;
    private String address;

    public House(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

 

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        long startTime=System.currentTimeMillis();
        for(int i=0;i<100000000;i++){
            new House("羊羊家","地球上");
        }
        System.out.println(System.currentTimeMillis()-startTime);

        startTime=System.currentTimeMillis();
        House house=new House("羊羊家","地球上");
        for(int i=0;i<100000000;i++){
            house.clone();
        }
        System.out.println(System.currentTimeMillis()- startTime);
    }
} 

 

運行結果:

從上圖我們可以看到採用new方法,只用了13毫秒,而採用原型模式,卻花了接近900毫秒,但是將House類的構造方法做一點點改變,我們就能發現差別,如下:

public class House implements Serializable,Cloneable {
    private String name;
    private String address;

    public House(String name, String address) {
        this.name = name.substring(1);
        this.address = address;

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

 

運行結果:

從上述代碼可以看到,採用原型模式的時間比new方法的時間少了一半左右。

結語

原型模式屬於創建型模式,能產生新的對象。

其對複製功能進行優化,採用Java提供的native方法clone,前提是實現cloneable接口。

使用默認的clone方法,是淺拷貝,只是複製值,實際上地址並沒有變化。如果想要深拷貝,可以重寫clone方法或採用序列化和反序列化。

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