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