设计模式(3)-- 原型模式 (clone分析)
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型来创建对象。
在java中有语言级别的支持:clone
在java中使用原型模式是非常简单的事情,实现Cloneable接口,调用Object的clone方法,便可以实现对象的拷贝。
浅复制:被复制的对象的值与原对象的值是相同的,对其他对象的复制仅仅是该对象的引用
深复制:被复制的对象的值与原对象的值是相同的,对其他对象的复制不是引用原有对象的引用,而是重新复制了该对象。
浅复制:
/** * 浅复制 * 1.在派生类的clone()方法中,调用super.clone()
* 2.派生类中实现Cloneable接口,否则会报CloneNotSupportedException错误
* 3.克隆对象与原对象不是同一个对象 克隆对象与原对象的类型一样 * @author junjin4838 * */ public class ShallowStudent implements Cloneable { private String name; private int age; Professor p;
public ShallowStudent2(String name, int age, Professor p) {
this.name = name;
this.age = age;
this.p = p;
} public Object clone(){ ShallowStudent o = null; try { o = (ShallowStudent)super.clone(); } catch (CloneNotSupportedException e) { System.out.println(e.toString()); } return o; } public static void main(String[] args) { People people1 = new People("test1",23);
ShallowStudent s1 = new ShallowStudent("jibingkun",23,people1); ShallowStudent s2 = (ShallowStudent)s1.clone(); s2.name = "zhangtianyu"; s2.age = 22;
s2.people.peopleName = "test2";
s2.people.peopleAge = 123; System.out.println("s1 name: "+s1.name + " s1 age: "+s1.age + "s1 people.name "+ s1.people.peopleName + "s1 people.age "+ s1.people.peopleAge);
System.out.println("s2 name: "+s2.name + " s2 age: "+s2.age + "s2 people.name "+ s2.people.peopleName + "s2 people.age "+ s2.people.peopleAge); boolean b1 = s1.equals(s2); boolean b2 = (s1.getClass())== (s2.getClass()); System.out.println(b1); System.out.println(b2); }
class Professor {
String name;
int age;
public Professor(String name, int age) {
this.name = name;
this.age = age;
}
}
------》》》-------
s1 name: jibingkun s1 age: 23 s1 people.name test2 s1 people.age 123
s2 name: zhangtianyu s2 age: 22 s2 people.name test2 s2 people.age 123
false
true
-----《《《---------
从中可以看出,调用Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的.
但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。
大多时候,这种clone的结果往往不是我们所希望的结果,这种clone也被称为”影子clone”。
Object.clone()的源码实现:
/** * Creates and returns a copy of this object. * x.clone() != x will be true * x.clone().getClass() == x.getClass() will be true} * if the class of this object does not implement the interface {@code Cloneable}, then a{@code CloneNotSupportedException} is thrown.
* 是native方法,native一般要高于非native的方法,因此使用clone往往会比new一个对象的效率要高
* 是protected属性的方法,这也意味着如果要应用 clone()方法,必须继承Object类,在Java中所有的类是缺省继承Object类的,也就不用关心这点了
* 重载clone()方法。还有一点要考虑的是为了让其它类能调用这个clone类的clone()方法,重载之后要把clone()方法的属性设置为public。
* Cloneable接口是不包含任何方法的!其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对Object类中clone()方法的。
* 如果clone类没有实现Cloneable接口,并调用了Object的 clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出 CloneNotSupportedException异常。 */ protected native Object clone() throws CloneNotSupportedException;
深复制(实现深度拷贝,则需要将实现了Cloneable接口并重写了clone方法的类中,所有的引用类型也全部实现Cloneable接口并重写clone方法,而且需要将引用类型的属性全部拷贝一遍。)
/** * 深复制
* */ public class ShallowStudent implements Cloneable {
private String name;
private int age;
public People people;
public ShallowStudent(){
this.name = "test1";
this.age = 23;
this.people = new People("test1",23);
}
public People getPeople(){
return people;
}
public Object clone(){
ShallowStudent o = null;
try {
o = (ShallowStudent)super.clone();
//子对象也要进行复制
o.people = (People) this.people.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.toString());
}
return o;
}
public static void main(String[] args) {
ShallowStudent s1 = new ShallowStudent();
ShallowStudent s2 = (ShallowStudent)s1.clone();
s2.name = "test2";
s2.age = 22;
System.out.println("s1 name: "+s1.name + " s1 age: "+s1.age + " People: " + s1.getPeople());
System.out.println("s2 name: "+s2.name + " s2 age: "+s2.age + " People: " + s2.getPeople());
}
}
class People implements Cloneable{
public String peopleName;
public int peopleAge;
public People(String peopleName,int peopleAge){
this.peopleName = peopleName;
this.peopleAge = peopleAge;
}
public Object clone(){
People o = null;
try {
o = (People)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
---------》》》---------
s1 name: test1 s1 age: 23 People: com.pingan.haofang.fjs.web.action.People@15db9742
s2 name: test2 s2 age: 22 People: com.pingan.haofang.fjs.web.action.People@6d06d69c
--------《《《----------
从原型模式的使用方式不难推断出,原型模式常使用于以下场景:
1、对象的创建非常复杂,可以使用原型模式快捷的创建对象。
2、在运行过程中不知道对象的具体类型,可使用原型模式创建一个相同类型的对象,或者在运行过程中动态的获取到一个对象的状态。
下面我们来看下原型模式的主要优点:
1、由于clone方法是由虚拟机直接复制内存块执行,所以在速度上比使用new的方式创建对象要快。
2、可以基于原型,快速的创建一个对象,而无需知道创建的细节。可以在运行时动态的获取对象的类型以及状态,从而创建一个对象。
然而原型模式的缺点也是相当明显的,主要的缺点就是实现深度拷贝比较困难,需要很多额外的代码量。
不过实际当中我们使用原型模式时,也可以写一个基类实现Cloneable接口重写clone方法,然后让需要具有拷贝功能的子类继承自该类,这是一种节省代码量的常用方式。
JDK的StringBuffer类没有重写clone()方法,还是final类型的,说明不能用继承的方法来实现clone()方法。
如果一个类中包含有StringBuffer类型对象或和 StringBuffer相似类的对象,我们有两种选择:要么只能实现影子clone,要么就在类的clone()方法中加一句(假设是 SringBuffer对象,而且变量名仍是p): o.p = new StringBuffer(p.toString());
利用串行化来做深复制
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamField; import java.io.Serializable; /** * 通过串行化实现深复制 * @author junjin4838 * */ public class ShallowStudent implements Serializable { private String name; private int age; public People people; public ShallowStudent(String name,int age,People people){ this.name = name; this.age = age; this.people = people; } public People getPeople(){ return people; } public Object deepClone() throws IOException, ClassNotFoundException{ ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream oo = new ObjectOutputStream(bo); //从流里面读出数据 oo.writeObject(this); ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray()); ObjectInputStream oi = new ObjectInputStream(bi); return (oi.readObject()); } public static void main(String[] args) throws ClassNotFoundException, IOException { People people = new People("test1",20); ShallowStudent s1 = new ShallowStudent("test2",21,people); ShallowStudent s2 = (ShallowStudent) s1.deepClone(); s2.name = "test3"; s2.age = 22; System.out.println("s1 name: "+s1.name + " s1 age: "+s1.age + " People: " + s1.getPeople()); System.out.println("s2 name: "+s2.name + " s2 age: "+s2.age + " People: " + s2.getPeople()); } } class People implements Serializable{ public String peopleName; public int peopleAge; public People(String peopleName,int peopleAge){ this.peopleName = peopleName; this.peopleAge = peopleAge; } }
-----------》》》---------
s1 name: test2 s1 age: 21 People: com.pingan.haofang.fjs.web.action.People@232204a1
s2 name: test3 s2 age: 22 People: com.pingan.haofang.fjs.web.action.People@4554617c
两个对象是不一样的
----------《《《----------