创建型模式——原型模式
(一)概述
原型模式指用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。原型实例指定了要创建的对象的种类,用这种方式创建对象非常高效,无须知道对象创建的细节。
原型模式是在内存中二进制流的拷贝,要比直接new一个对象性能好很多,尤其是构造函数比较复杂,并且在循环体中生产出大量的对象时,用原型模式效率很高。
说起原型模式的浅拷贝和深拷贝, 我们都知道浅拷贝是对值类型的成员变量进行复制, 对引用类型的变量只是对引用进行复制,实际上两个对象还是指向的同一实例。而深拷贝不仅值类型的成员变量进行复制,,还对引用类型的成员变量申请存储空间,让他成为一个新对象。知道这些还是不够的,,我们要明白其原理,下面来看一下他们的实现过程。
(二)浅拷贝版
要实现浅拷贝我们首先要实现Cloneable接口,这个接口里一个方法都没有, 他的作用的就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone()方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝。
public class Sheep implements Cloneable {
private String name;
private int age;
private String color;
private Sheep friend;
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;
}
public Sheep getFriend() {
return friend;
}
public void setFriend(Sheep friend) {
this.friend = friend;
}
@Override
protected Object clone() {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return sheep;
}
}
然后我们来测试一下,通过两个hashcode的值我们不难发现,我们原本的对象和克隆的对象并不是同一个对象,说明我们值类型的成员变量是复制出来一份的。
public class Test {
public static void main(String[] args) {
Sheep sheep = new Sheep("tom", 1, "白色");
sheep.setFriend(new Sheep("jack", 2, "黑色"));
Sheep sheep2 = (Sheep)sheep.clone();
System.out.println(sheep.hashCode()); //输出结果为 455896770
System.out.println(sheep2.hashCode()); //输出结果为 1323165413
}
}
我们可以再来看一个测试样例,在这个例子中我们就能发现浅拷贝存在的问题。我们发现,两个对象中引用类型friend却是同一个,由此可见浅复制对引用类型只是复制了引用,并没有开辟新的存储空间。因此,我们在遇到这种情况时需要使用深拷贝。
public class Test {
public static void main(String[] args) {
Sheep sheep = new Sheep("tom", 1, "白色");
sheep.setFriend(new Sheep("jack", 2, "黑色"));
Sheep sheep2 = (Sheep) sheep.clone();
System.out.println(sheep.getFriend().hashCode());//输出结果为 455896770
System.out.println(sheep2.getFriend().hashCode());//输出结果为 455896770
}
}
(三)深拷贝版
深拷贝我们有两种方法可以选择,一是给所有的引用类型属性的类都实现Cloneable接口,很明显,如果我们的引用类型很多,那么这种方法会带来代码量的急剧增长,同时也加大了出错的可能性。二是通过序列化和反序列化的方式来进行深拷贝,这个方法会更简便,不过需要注意的是,所有的引用类型属性也必须是可序列化的,否则会导致此类不能序列化。。
我们现在就开始将上面浅拷贝的clone方法进行修改,变成深拷贝。我们不再需要实现Cloneable接口,而我们需要实现Serializable接口,以满足序列化和反序列化的需要。
public class Sheep implements Serializable {
private String name;
private int age;
private String color;
private Sheep friend;
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;
}
public Sheep getFriend() {
return friend;
}
public void setFriend(Sheep friend) {
this.friend = friend;
}
public Object deepClone() {
//创建流对象
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);
return (Sheep) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
}
}
我们来测试一下效果,很明显,和浅拷贝不同,两个对象中的引用类型指向了不同的对象。这里有个小细节可以了解一下,经过测试我们发现,反序列化并不会调用构造方法。反序列的对象是由JVM自己生成的对象,不通过构造方法生成。
public class Test {
public static void main(String[] args) {
Sheep sheep = new Sheep("tom", 1, "白色");
sheep.setFriend(new Sheep("jack", 2, "黑色"));
Sheep sheep2 = (Sheep) sheep.deepClone();
System.out.println(sheep.getFriend().hashCode());//输出结果为 670576685
System.out.println(sheep2.getFriend().hashCode());//输出结果为 1323468230
}
}
2020年7月28日