克隆羊多利之原型模式

今天就不想开头了,直接来吧。如果想要创建几个和某对象一模一样的新对象,我们很容易想到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方法或采用序列化和反序列化。

posted @ 2020-05-29 10:22  学习Java的小姐姐  阅读(377)  评论(0编辑  收藏  举报