1. 前言
在介绍原型模式之前,我们先举一个小例子来引出我们要讲述的内容。
在平常,如果我们想要克隆多个相同属性的对象时,我们通常会使用下面的方式进行克隆。
我们要用到的Person类
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
Person(){
System.out.println("空参函数调用");
}
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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
当我们想要克隆多个一模一样的对象时,测试类会这样写
@Test
public void test1() throws CloneNotSupportedException {
Person person1 = new Person("张三", 22);
Person person2 = new Person(person1.getName(), person1.getAge());
Person person3 = new Person(person1.getName(), person1.getAge());
Person person4 = new Person(person1.getName(), person1.getAge());
System.out.println(person1);
System.out.println(person2);
System.out.println(person3);
System.out.println(person4);
}
这种方式的缺点很明显,就是我们每次都要获取原始对象的成员变量,当对象的成员变量很多时,这种方式就显得特别麻烦,效率很低。
下面所讲述的原型模式就是用来解决这个问题的。
2. 原型模式
2.1 原型模式介绍
原型模式介绍
原型模式是一种创建型设计模式,使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,主要用于创建重复的对象,同时又可以保证性能。
应用场景
当创建对象成本较大时,可以使用原型模式对已有对象进行复制来获得。
使用
实现Cloneable接口,重写clone()方法。
在具体使用之前,我们需要先了解一下浅拷贝和深拷贝
对于浅拷贝来说,当成员变量是引用类型时,浅拷贝拷贝的是地址;当成员变量是基本类型时,浅拷贝拷贝的是值。
对于深拷贝来说,无论成员变量是基本类型还是引用类型,采用的都是值传递,而不是拷贝了一个地址。
2.2. 浅拷贝
2.2.1 浅拷贝例子1
下面我们举一个浅拷贝的例子:
让Person类实现Cloneable接口,重写clone()方法
package antetype;
import java.util.ArrayList;
public class Person implements Cloneable{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
Person(){
System.out.println("空参函数调用");
}
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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public Person clone() throws CloneNotSupportedException {
return (Person)super.clone();
}
}
测试类
@Test
public void test2() throws CloneNotSupportedException {
Person person1 = new Person();
person1.setName("张三");
person1.setAge(22);
Person person2 = person1.clone();
System.out.println("person1" + " " + person1 + " " + "hashcode:" + person1.hashCode());
System.out.println("person2" + " " + person2 + " " + "hashcode:" + person2.hashCode());
System.out.print("两个Person对象地址是否相同:");
System.out.println(person1 == person2);
System.out.println("修改person2:");
person2.setName("李四");
System.out.println("person1" + " " + person1 + " " + "hashcode:" + person1.hashCode());
System.out.println("person2" + " " + person2 + " " + "hashcode:" + person2.hashCode());
}
结果
空参函数调用
person1 Person{name='张三', age=22} hashcode:463345942
person2 Person{name='张三', age=22} hashcode:195600860
两个Person对象地址是否相同:false
修改person2:
person1 Person{name='张三', age=22} hashcode:463345942
person2 Person{name='李四', age=22} hashcode:195600860
从结果中我们可以看到,person2是对person1的复制,两个对象的成员变量的值一模一样,且两个对象的地址不同,当我们修改克隆生成的person2时,我们可以发现person1的成员变量并没有改变,因此对于类型是基本类型的成员变量来说,拷贝的是值,而不是拷贝了一个地址,且拷贝的新成员变量并不是经过构造方法拷贝出的。
2.2.2 浅拷贝例子2
接下来我们说一下浅拷贝的例子2,我们在Person类中加一个List集合。
Person类
public class Person implements Cloneable{
private String name;
private int age;
private ArrayList list;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
Person(){
System.out.println("空参函数调用");
}
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 ArrayList getList() {
return list;
}
public void setList(ArrayList list) {
this.list = list;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", list=" + list +
'}';
}
@Override
public Person clone() throws CloneNotSupportedException {
return (Person)super.clone();
}
}
测试类
@Test
public void test3() throws CloneNotSupportedException {
Person person1 = new Person();
person1.setName("张三");
person1.setAge(22);
person1.setList(new ArrayList());
Person person2 = person1.clone();
List list1 = person1.getList();
list1.add(111);
list1.add(222);
System.out.println("person1" + " " + person1 + " " + "hashcode:" + person1.hashCode() + " " + "list" + person1.getList());
System.out.println("person2" + " " + person2 + " " + "hashcode:" + person2.hashCode() + " " + "list" + person1.getList());
System.out.print("两个Person对象地址是否相同:");
System.out.println(person1 == person2);
System.out.print("两个Person对象中的list对象的地址是否相同:");
System.out.println(person1.getList() == person2.getList());
System.out.println("修改person2中的List:");
List list2 = person2.getList();
list2.add(333);
System.out.println("修改后两个对象中的list再次比较:");
System.out.println("person1的list" + person1.getList());
System.out.println("person2的list" + person2.getList());
System.out.print("两个对象的list地址是否相同:");
System.out.println(person1.getList() == person2.getList());
}
结果
空参函数调用
person1 Person{name='张三', age=22, list=[111, 222]} hashcode:463345942 list[111, 222]
person2 Person{name='张三', age=22, list=[111, 222]} hashcode:195600860 list[111, 222]
两个Person对象地址是否相同:false
两个Person对象中的list对象的地址是否相同:true
修改person2中的List:
修改后两个对象中的list再次比较:
person1的list[111, 222, 333]
person2的list[111, 222, 333]
两个对象的list地址是否相同:true
结论
从上面的执行结果中我们其实可以得出以下结论:
当我们对Person对象进行克隆时,可以看成是new了一个Person对象(其实并没有调用构造方法),对于基本类型的成员变量采用的是值传递,拷贝的是值,而对于引用类型的成员变量浅拷贝仅仅拷贝的是一个地址,对象之间共享这个引用类型的成员变量。
2.3 深拷贝
方法一
对于引用类型的成员变量也实现拷贝就好了,也就是无限递归下去,不再赘述,可以参考下面博客的链接。
方法二
通过对象序列化和反序列化首先深拷贝(推荐)
深拷贝
首先Person对象实现Serializable接口,然后自定义拷贝方法deepClone()
Person类
package antetype;
import jdk.nashorn.internal.ir.SplitReturn;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class Person implements Cloneable, Serializable {
private String name;
private int age;
private ArrayList list;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
Person(){
System.out.println("空参函数调用");
}
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 ArrayList getList() {
return list;
}
public void setList(ArrayList list) {
this.list = list;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", list=" + list +
'}';
}
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person)super.clone();
}
//深拷贝
public Person 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);
Person person = (Person) ois.readObject();
return person;
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
测试类
@Test
public void test4() throws CloneNotSupportedException {
Person person1 = new Person();
person1.setName("张三");
person1.setAge(22);
person1.setList(new ArrayList());
Person person2 = person1.deepClone();
System.out.print("两个对象的list地址是否相同:");
System.out.println(person1.getList() == person2.getList());
}
结果
空参函数调用
两个对象的list地址是否相同:false
3. 总结
- 创建新的对象比较复杂时,可以使用原型模式简化对象的创建过程,同时也可以提升效率。
- 如果原始对象发生变化(增加或减少属性),其他克隆对象也会发生相应的变化,无需修改代码。
- 如果成员变量没有引用类型,只使用浅拷贝即可;如果引用类型的成员变量很少,可考虑递归实现clone,否则推荐序列化。