原型模式是用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。是不是很难理解?其实简单一句话就可以基本上概括它了:本身就是克隆,通过克隆的方式来获取一个新对象。
原型模式有两种克隆方式:浅克隆和深克隆两种
浅克隆
有一个Person类,它有age和name两个属性:
public class Person implements Cloneable {
private int age;
private String name;
public Object clone(){
Person person = null;
try {
//进行克隆
person = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return person;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
要点:Person类首先要继承Cloneable接口, 然后重写clone方法。
下面做个测试
public class Test {
public static void main(String[] args) {
Person person = new Person();
person.setAge(10);
person.setName("ming");
System.out.println("person "+person.toString());
Person person1 = (Person) person.clone();
System.out.println("clone person "+person1.toString());
}
}
结果如下:
person Person{age=10, name=’ming’}
clone person Person{age=10, name=’ming’}
可以看出,浅克隆复制出来了一个一模一样的完整对象。
但是浅克隆有一定的问题,如果在类中的成员变量含有地址引用的变量,比如数组,list,其他类的实例等,这样浅克隆得到的对象的地址引用和原对象是一样,如果原对象的变量发生了更改,克隆得到的变量也会发生变化。这样的问题就得有深克隆来解决了。
深克隆
public class DeepClone implements Cloneable{
private int age;
private String name;
private ArrayList<Integer> list;
public Object clone(){
DeepClone deepClone = null;
try {
deepClone = (DeepClone) super.clone();
deepClone.list = (ArrayList<Integer>) this.list.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return deepClone;
}
public void setList(ArrayList<Integer> list) {
this.list = list;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Integer> getList() {
return list;
}
@Override
public String toString() {
return "DeepClone{" +
"age=" + age +
", name='" + name + '\'' +
", list=" + list +
'}';
}
}
测试:
public class Test2 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
DeepClone deepClone = new DeepClone();
deepClone.setAge(10);
deepClone.setName("ming");
deepClone.setList(list);
DeepClone deepClone1 = (DeepClone) deepClone.clone();
System.out.println(deepClone.toString());
System.out.println(deepClone1.toString());
System.out.println("_______________________________");
//改变其中一个地址引用变量的值
list.add(3);
System.out.println(deepClone.toString());
System.out.println(deepClone1.toString());
}
}
得到的结果是:
DeepClone{age=10, name=’ming’, list=[1, 2]}
DeepClone{age=10, name=’ming’, list=[1, 2]}
DeepClone{age=10, name=’ming’, list=[1, 2, 3]}
DeepClone{age=10, name=’ming’, list=[1, 2]}
这样就可以看到在深克隆的情况下,如果改变原有地址引用变量的值,并不是影响到新克隆对象的值。两种对象互为独立。
注意: 要使用clone方法,类的成员变量上不要增加final关键字。因为final类型是不允许重赋值的。
优点
(1)原型模式是在内存中二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量对象时,原型模式可能更好的体现其优点。
(2)还有一个重要的用途就是保护性拷贝,也就是对某个对象对外可能是只读的,为了防止外部对这个只读对象的修改,通常可以通过返回一个对象拷贝的形式实现只读的限制。
缺点
1)这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的,在实际开发中应该注意这个潜在问题。优点是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。
(2)通过实现Cloneable接口的原型模式在调用clone函数构造实例时并不一定比通过new操作速度快,只有当通过new构造对象较为耗时或者说成本较高时,通过clone方法才能够获得效率上的提升。