在创建一个对象的时候,通常会使用到类中的构造方法,也就是说,对象是从类中创建的。但是在某些特殊情况下,是不允许或不希望直接调用构造方法,即不通过类来创建对象,怎么办呢?我们就可以通过已经创建好的对象“克隆”出新的对象,即不通过类来创建实例,而通过实例来创建实例,这就是原型模式。
原型模式使用的场景如下:
- 类初始化消耗资源较多,构造方法比较复杂;
- 需要在循环体中大量创建对象。
所有的类都继承了Object类,而Object类中默认提供了clone方法,通过实现Cloneable接口并重写clone方法即可实现对象克隆。在使用原型模式时,根据其成员变量是否也需要克隆,原型模式又分为:浅拷贝和深拷贝。
代码示例如下:
浅克隆
public class Student implements Cloneable, Serializable {
private int age;
private Date birthday;
public Student(int age, Date birthday) {
this.age = age;
this.birthday = birthday;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
return student;
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
Student s1 = new Student(1, new Date());
Student s2 = (Student) s1.clone();
System.out.println(s1 == s2);
System.out.println(s1.getBirthday() == s2.getBirthday());
}
}
false
true
可以看出,s1和s2的引用地址不同,所以s2是克隆出来的新对象,但为什么说这种方式是浅克隆呢?因为如果Student的成员变量中含有引用对象类型的话,它的成员变量的地址引用还是指向原来的对象,所以是s1和s2的birthday属性的引用地址是相同的。如果修改了s1的birthday属性,就会同时修改s1的birthday属性。所以,如果需要克隆一个全新的对象,我们就需要使用深克隆,把对应的引用对象也相应克隆一份。
深克隆
@Override
protected Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.birthday = (Date) birthday.clone();
return student;
}
我们只需要在clone方法中把对应的birthday属性克隆一份,就可以实现深克隆。
序列化和反序列化实现深克隆
除了把对应的引用类型的成员变量进行克隆这一方法外,我们还可以使用序列化和反序列化的方法实现深克隆,代码如下:
@Override
protected Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
Student cloneStudent = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(student);
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
cloneStudent = (Student) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return cloneStudent;
}
使用序列化和反序列化可以很方便的实现深克隆,而且不用关心引用对象嵌套的问题。