用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
定义
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
Prototype原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。
解决问题
它主要面对的问题是:“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有比较稳定一致的接口。
浅复制,深复制
讲到原型模式了,我们就不得不区分两个概念:深拷贝、浅拷贝。
浅拷贝:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝。
被复制的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
深拷贝:当一个类的拷贝构造方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。
如下图:
原型模式结构
原型模式主要包含如下三个角色:
Prototype:抽象原型类。声明克隆自身的接口。
ConcretePrototype:具体原型类。实现克隆的具体操作。
Client:客户类。让一个原型克隆自身,从而获得一个新的对象。
我们都知道Object是祖宗,所有的Java类都继承至Object,而Object类提供了一个clone()方法,该方法可以将一个java对象复制一份,因此在java中可以直接使用clone()方法来复制一个对象。但是需要实现clone的Java类必须要实现一个接口:Cloneable.该接口表示该类能够复制且具体复制的能力,如果不实现该接口而直接调用clone()方法会抛出CloneNotSupportedException异常。
案例分析
我有person类和一个Pig类,person中引用pig,pig类是一个空类,继承Cloneable接口,重写object类的clone方法,具体结构如下
public class Person implements Cloneable,Serializable {
private String name;
private Integer age;
private String sex;
private Pig pig = new Pig();
public Person() {
}
public Person(String name, Integer age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Pig getPig() {
return pig;
}
public void setPig(Pig pig) {
this.pig = pig;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", pig=" + pig +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Person) {
Person person = (Person) obj;
return (person.name == name || (null != name && person.name.equals(name))) &&
(person.age.equals(age) || (null != age && person.age.equals(age))) &&
(person.sex == sex || (null != sex && person.sex.equals(sex))) &&
(person.pig == pig || (null != pig && person.pig.equals(pig)));
}
return false;
}
}
pig类
public class Pig implements Cloneable,Serializable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
下面进行测试clone功能
/**
* 测试clone模式
*/
public static void testOne(){
Person personOne = new Person("javaboy",18,"男");
log.info("personOne:{}",personOne);
try {
Person personTwo = (Person) personOne.clone();
log.info("personTwo:{}",personTwo);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
执行结果如下
personOne:Person{name='javaboy', age=18, sex='男', pig=com.guhui.gof23.prototype.Pig@439f5b3d}
personTwo:Person{name='javaboy', age=18, sex='男', pig=com.guhui.gof23.prototype.Pig@439f5b3d}
可以看到,clone是成功的。但这只是一个浅复制,可以看下具体信息。
/**
* 测试clone模式-浅复制
*/
public static void testTwo(){
Person personOne = new Person("javaboy",18,"男");
log.info("personOne:{}",personOne);
try {
Person personTwo = (Person) personOne.clone();
log.info("personTwo:{}",personTwo);
//查看原型实例和clone实例的hashCode
log.info("personOne.hashCode():{},personTwo.hashCode():{}",personOne.hashCode(),personTwo.hashCode());
//查看原型实例的pig和clone实例的pig 的hashCode
log.info("personOne.Pig.hashCode:{},personTwo.Pig.hashCode:{}",personOne.getPig().hashCode(),personTwo.getPig().hashCode());
//查看原型实例和clone实例 是否是同一个对象
log.info("personOne == personTwo ??? {}",personOne == personTwo);
//看看原型实例和clone实例 是否类型一样。
log.info("personOne.class == personTwo.class ??? {}",personOne.getClass() == personTwo.getClass());
//查看两个对象是否相等,抽象equest方法,比较内容
log.info("personOne.class equest personTwo.class ??? {}",personOne.equals(personTwo));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
执行结果如下
personOne:Person{name='javaboy', age=18, sex='男', pig=com.guhui.gof23.prototype.Pig@439f5b3d}
personTwo:Person{name='javaboy', age=18, sex='男', pig=com.guhui.gof23.prototype.Pig@439f5b3d}
personOne.hashCode():565760380,personTwo.hashCode():6566818
personOne.Pig.hashCode:1134517053,personTwo.Pig.hashCode:1134517053
personOne == personTwo ??? false
personOne.class == personTwo.class ??? true
personOne.class equest personTwo.class ??? true
可以看到,虽然两个的hashCode不相等,不是同一个对象,但是通过equest比较,确是相等的,两个对象的pig属性的hashCode值也相等。证明两个对象的属性指向同一个地址
下面我们看看深复制。深复制我利用序列化来执行。所以person和pig类都实现了Serializable接口
/**
* 测试clone模式-深复制
*/
public static void testThree(){
Person personOne = new Person("javaboy",18,"男");
log.info("personOne:{}",personOne);
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(personOne);
byte[] bytes = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
Person personTwo = (Person) ois.readObject();
log.info("personTwo:{}",personTwo);
log.info("personOne.hashCode():{},personTwo.hashCode():{}",personOne.hashCode(),personTwo.hashCode());
log.info("personOne.Pig.hashCode:{},personTwo.Pig.hashCode:{}",personOne.getPig().hashCode(),personTwo.getPig().hashCode());
log.info("personOne == personTwo ??? {}",personOne == personTwo);
log.info("personOne.class == personTwo.class ??? {}",personOne.getClass() == personTwo.getClass());
log.info("personOne.class equest personTwo.class ??? {}",personOne.equals(personTwo));
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
结果如下
personOne:Person{name='javaboy', age=18, sex='男', pig=com.guhui.gof23.prototype.Pig@439f5b3d}
personTwo:Person{name='javaboy', age=18, sex='男', pig=com.guhui.gof23.prototype.Pig@484b61fc}
personOne.hashCode():1029991479,personTwo.hashCode():1174290147
personOne.Pig.hashCode:1134517053,personTwo.Pig.hashCode:1212899836
personOne == personTwo ??? false
personOne.class == personTwo.class ??? true
personOne.class equest personTwo.class ??? false
从执行结果,可以看到,两个对象的hashCode值不同,属性pig的hashCode也不相等,并且通过equest的判断,得到结果为false。证明两个对象的属性指向的地址不是同一个,证明深复制成功。
new 和 clone 效率比较
下面我们看下new和clone效率比较问题。由于clone操作不会调用构造函数,是基于内存中的数据库copy,那是不是clone的效率一定比new高呢?我们测试一下。
我创建了一个空类,里面没有属性,只有一个空的构造函数和重写的clone函数
public class Laptop implements Cloneable {
public Laptop() { }
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
然后我们进行测试
public class LaptopTime {
public static void main(String[] args) {
//创建多少个对象
int size = 10000 * 10000;
try {
testNew(size);
testClone(size);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
public static void testNew(int size){
long start = System.currentTimeMillis();
for(int i=0;i<size;i++){
Laptop t = new Laptop();
}
long end = System.currentTimeMillis();
System.out.println("new的方式创建耗时:"+(end-start));
}
public static void testClone(int size) throws CloneNotSupportedException{
long start = System.currentTimeMillis();
Laptop t = new Laptop();
for(int i=0;i<size;i++){
Laptop temp = (Laptop) t.clone();
}
long end = System.currentTimeMillis();
System.out.println("clone的方式创建耗时:"+(end-start));
}
}
案例中循环了一亿次,从而new了一亿个对象和clone了一亿个对象。执行结果如下
new的方式创建耗时:9
clone的方式创建耗时:2483
可以看到,clone消耗的时间远远超过new消耗的时间,达到了惊人的276倍。
显然jvm的开发者也意识到通过new方式来生成对象占据了开发者生成对象的绝大部分,所以对于利用new操作生成对象进行了优化。
然后我们测试一下深复制的效率
对Laptop类进行稍微的修改,代码如下:
public class Laptop implements Cloneable {
private Pig pig;
private Person person;
private Pig pig2;
private Person person2;
public Laptop() {
pig = new Pig();
person = new Person("javaboy",18,"男");
pig2 = new Pig();
person2 = new Person("javaboy",18,"男");
}
@Override
protected Object clone() throws CloneNotSupportedException {
Laptop clone = (Laptop) super.clone();
clone.pig = (Pig) pig.clone();
clone.person = (Person) person.clone();
clone.pig2 = (Pig) pig2.clone();
clone.person2 = (Person) person2.clone();
return clone;
}
}
在laptop中新加四个对象属性,在clone方法中实现深复制,执行效果如下
new的方式创建耗时:4779
clone的方式创建耗时:4884
同样一亿次循环创建对象。两者之间的效率已经相当接近了。
结论: 可以发现,“深复制”并没有clone方法的效率!相反,这种方法此时比通过new构造对象的方法效率还低。
同样,我们把laptap中clone方法改为浅复制
@Override
protected Object clone() {
return super.clone();
}
属性不改动,再次执行,结果如下
new的方式创建耗时:4576
clone的方式创建耗时:913
可以看出,clone的效率要高于new。
至此,基本可以确定,clone方法只有在进行 复杂 的浅复制时效率才会明显高于new构造方式,但由于此时的clone本质上依旧是浅复制,因此需要注意引用的指向问题,避免错误更改关联的信息值。
代码案例文件:
http://pvyob7cem.bkt.clouddn.com/prototype.rar