原型模式
1.本质
对已经存在的对象模型进行拷贝
- 应用场景
类初始化消耗资源较多的情况下
new对象非常繁琐的过程
构造函数复杂
循环体中产生大量对象时??
2.分类:
2.1浅克隆
- 特点
拷贝的是对象的引用,而不是对象的值。如果克隆的对象发生了变化,原对象也会发生改变,对代码存在风险
- 实现
JDK提供了Cloneable接口,用户只需要实现这个接口,重写Object类的clone方法,然后执行super.clone() 就可以拷贝对象了。注意的是,Object类的clone方法是native方法
2.2深克隆
- 特点
拷贝的是对象的值,而不是引用。拷贝对象和原对象没有任何关系。如果原对象中有一个引用类型的属性,拷贝之后,这个引用类型的属性发生了变化,不会影响到原对象的引用类型属性的值。
- 实现
方式1: 对原对象,实现Serializable接口,利用对象的序列化和反序列化来完成对象的拷贝(参考单例模式中的序列化破坏单例的举例)
方式2:通过json实现克隆
这两种方式拷贝,都不需要实现Cloneable接口
- 注意点
克隆可能会破坏单例,需要在代码中规避这种风险,如果是单例,就不要实现Cloneable接口。
JDK中,凡是实现了Cloneable接口的clone方法,都是浅克隆,深克隆需要自行实现
3.浅克隆举例
package com.gupaoedu.vip.pattern.prototype.shallowclone;
import lombok.Data;
import lombok.ToString;
import java.util.List;
/**
* 浅克隆: 实现Cloneable接口,重写clone方法
*/
@Data
@ToString
public class ConcretePrototype implements Cloneable {
private int age;
private String name;
private List<String> hobbies;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
测试类:在克隆出对象后,修改克隆对象的hobbies属性,然后再去对比原对象的hobbies:
package com.gupaoedu.vip.pattern.prototype.shallowclone;
import java.util.ArrayList;
public class ShallowCloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("jack");
ArrayList<String> hobbies = new ArrayList<>();
hobbies.add("reading");
prototype.setHobbies(hobbies);
System.out.println("源原对象:" + prototype);
ConcretePrototype copy = (ConcretePrototype) prototype.clone();
// 克隆完成后,给克隆对象添加特有的属性
copy.getHobbies().add("swimming");
System.out.println("克隆对象:" + copy);
System.out.println("修改克隆对象后,源对象:" + prototype);
System.out.println("prototype == copy? " + (prototype == copy));
System.out.println("prototype.hobbies == copy.hobbies ? " + (prototype.getHobbies() == copy.getHobbies()));
}
}
测试结果
我们仅仅期望修改一下克隆出来的对象的属性,结果却把原对象的hobbies属性也修改了,这就是浅克隆的体现,并且prototype.hobbies == copy.hobbies 结果为true,说明拷贝的是hobbies的地址,而不是hobbies这个list的内容。因此,浅拷贝其实仅仅是复制了对象的引用地址,并不是真正的复制了对象的属性。
4. 深克隆举例
package com.gupaoedu.vip.pattern.prototype.deepclone;
import lombok.Data;
import lombok.ToString;
import java.io.*;
import java.util.List;
@Data
@ToString
public class ConcretePrototype implements Serializable {
private int age;
private String name;
private List<String> hobbies;
public ConcretePrototype deepClone(){
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ConcretePrototype clone =(ConcretePrototype) ois.readObject();
return clone;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
package com.gupaoedu.vip.pattern.prototype.deepclone;
import java.util.ArrayList;
public class DeepCloneTest {
public static void main(String[] args) {
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("jack");
ArrayList<String> hobbies = new ArrayList<>();
hobbies.add("reading");
prototype.setHobbies(hobbies);
System.out.println("原对象:" + prototype);
ConcretePrototype copy = prototype.deepClone();
// 克隆完成后,给克隆对象添加特有的属性
copy.getHobbies().add("swimming");
System.out.println("克隆对象:" + copy);
System.out.println("修改克隆对象后,原对象:" + prototype);
System.out.println("prototype == copy? " + (prototype == copy));
System.out.println("prototype.hobbies == copy.hobbies ? " + (prototype.getHobbies() == copy.getHobbies()));
}
}
测试结果:
说明,对于原对象中的引用类型数据,拷贝的不再是引用的地址,因此克隆对象的改变,不会改变原来的对象。