原型模式
1. 概述
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象,通俗点说就是对象的拷贝,而这种拷贝又可以分为深拷贝与浅拷贝,往后我们会深入叙述。
2. 结构
原型模式包含如下角色:
- 抽象原型类:声明一个可以克隆自己的方法,通常是一个接口。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
3. 实现
原型模式的克隆分为浅克隆和深克隆,也就是深拷贝与浅拷贝。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于基本类型,就会拷贝基本类型的值,但是对于非基本类型属性,就会拷贝指向原有对象的内存地址,并不会开辟新的内存空间。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址,而是开辟新的内存空间保存数据。
3.1 浅克隆的实现
Java中的Object类中提供了 clone()
方法来实现浅克隆。 Cloneable 接口就是一个抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。现在我们新建一个教师类来实现 Cloneable 接口,并实现他的clone()方法,而后我们来测试浅拷贝。
代码如下:
Teacher(具体的原型类):
public class Teacher implements Cloneable{
private String name;
//实现克隆的方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Client(测试访问类):
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher();
Teacher teacher1 = (Teacher) teacher.clone();
System.out.println(teacher == teacher1);
}
}
通过输出结果为false,我们可以知道。通过克隆出来的对象已经成为了一个新的对象,并且拥有自己的内存空间,这是因为我们在我们的教师类之中只定义了基本类型,所以在使用 clone() 方法创建对象的时候就将基本类型的值复制了过来。
这种浅拷贝的作用类似于我们new一个对象,但是克隆的执行效率要比new高的多,因为调用 clone() 方法无需调用构造器就可以创建对象,所以在所克隆的类中没有引用类型的情况下,一次性的克隆大量的对象相比于new一个对象可以大大的提高效率。
既然他克隆对象也可以开辟新的内存空间,那么为什么叫他浅克隆呢,这就是因为引用类型变量的存在,我们新建一个学生类,而后在教师类中通过组合来使用这个类。
代码示例
学生类:
public class Student implements Cloneable{
private String name;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
教师类:
public class Teacher implements Cloneable{
private String name;
private Student stu;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
//实现克隆的方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
测试类:
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher();
Student student = new Student();
teacher.setStu(student);
Teacher teacher1 = (Teacher) teacher.clone();
System.out.println(teacher == teacher1);
System.out.println(teacher.getStu() == teacher1.getStu());
}
}
输出结果为
false
true
3.2 深克隆(使用重写clone方法的方式)
在以上的输出结果中我们可以分析到,在教师类中引用的学生类并没有完成克隆,只是复制了一份内存地址给新的教师类对象。
所以这也是浅克隆的弊端,如果使用这种方式克隆了多个教师类,但是学生对象始终只有一个,那么就会导致所有的教师对象共享这一个学生对象,无论那个教师对象修改了学生对象,所有教师对象中的学生类都会发生改变。
这显然是不可以的,所以为了避免这种情况,我们可以通过重写clone()方法的方式完成教师对象的深克隆。
代码如下:
教师类:
public class Teacher implements Cloneable{
private String name;
private Student stu;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
//实现克隆的方法
@Override
protected Object clone() throws CloneNotSupportedException {
Teacher teacher = (Teacher) super.clone();
teacher.setStu((Student) stu.clone());
return teacher;
}
}
测试类:
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher();
Student student = new Student();
teacher.setStu(student);
Teacher teacher1 = (Teacher) teacher.clone();
System.out.println(teacher == teacher1);
System.out.println(teacher.getStu() == teacher1.getStu());
}
}
输出结果为
false
false
通过输出结果我们可以看到,利用重写clone方法的方式,我们完成了教师类的深拷贝,但是对于这种深拷贝,当我们的对象一级一级循环嵌套的时候,我们就需要不断的去重新每个对象的 clone() 方法,例如在student类中引用了地址类,那么要完成深克隆我们又要需要去重写student类的 clone() 方法,显然这种时候,这种方法将会变得非常繁琐。
那么就要介绍一种可以完全实现深克隆的方法就是使用序列化的方式实现对象的深克隆。
3.3 深克隆(利用序列化的方式)
在单例模式的时候,我们讲解了为什么序列化会破坏我们的单例模式,原因是因为序列化可以通过反射创建新的对象,从而导致了单例模式的破坏。
但是这种破坏恰恰是原型模式所需要的一种方式。
通过使用序列化反序列化的方式我们可以完全的创建一个新的对象,而且这种方法会复制所有的变量类型,完全不需要担心类与类之间的嵌套组合。
代码示例:
public class Client {
public static void main(String[] args) throws Exception {
Teacher teacher = new Teacher();
Student student = new Student();
teacher.setStu(student);
outObject(teacher);
Teacher teacher1 = (Teacher)readObject();
System.out.println(teacher == teacher1);
System.out.println(teacher.getStu() == teacher1.getStu());
}
public static Teacher readObject() throws Exception{
//创建对象输入流
ObjectInputStream in = new ObjectInputStream(new FileInputStream("E:\\ziyuan\\object.txt"));
Teacher teacher = (Teacher) in.readObject();
in.close();
return teacher;
}
public static void outObject(Teacher teacher) throws Exception{
//创建对象输出流
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("E:\\ziyuan\\object.txt"));
out.writeObject(teacher);
out.close();
}
输出结果为:
false
false
注意:Citation类和Student类必须实现Serializable接口,否则会抛NotSerializableException异常。
通过以上输出结果我们可以看到,我们使用序列化完成了对象的深克隆。
4. 补充(new 对象与克隆的区别)
先看代码:
public class Client {
public static void main(String[] args) throws Exception {
Teacher teacher = new Teacher();
Student student = new Student();
teacher.setStu(student);
Teacher teacher1 = new Teacher();
System.out.println(teacher == teacher1);
System.out.println(teacher.getStu() == teacher1.getStu());
}
结果的输出是两个false,表面上来看他好像实现了对象的深克隆,但是实际上new一个对象是为所有的对象开辟了新的内存空间,也就是说他们是一个新的对象,正如new的中文意思表达的那样,而我们的深拷贝则不光可以实现新对象的创建还可以将原对象所拥有的值一起复制到新的对象当中。
来看代码:
public class Client {
public static void main(String[] args) throws Exception {
Teacher teacher = new Teacher();
Student student = new Student();
student.setName("name");
teacher.setStu(student);
Teacher teacher1 = new Teacher();
System.out.println(teacher == teacher1);
System.out.println(teacher.getStu().getName());
System.out.println(teacher1.getStu().getName());
System.out.println(teacher.getStu() == teacher1.getStu());
}
输出结果:
name
Exception in thread “main” java.lang.NullPointerException
at com.qgn.Client.main(Client.java:17)
出现异常的原因是因为我们的student对象是一个全新的对象,他并没有将旧对象的值拷贝过来,从而导致了出现空指针异常。