在創建一個對象的時候,通常會使用到類中的構造方法,也就是說,對象是從類中創建的。但是在某些特殊情況下,是不允許或不希望直接調用構造方法,即不通過類來創建對象,怎麼辦呢?我們就可以通過已經創建好的對象“克隆”出新的對象,即不通過類來創建實例,而通過實例來創建實例,這就是原型模式。
原型模式使用的場景如下:
- 類初始化消耗資源較多,構造方法比較複雜;
- 需要在循環體中大量創建對象。
所有的類都繼承了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;
}
使用序列化和反序列化可以很方便的實現深克隆,而且不用關心引用對象嵌套的問題。