概述
原型模式:Prototype Pattern,指原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象,属于创建型设计模式
原型模式的核心在于复制原型对象:
- 以系统中已存在的一个对象为原型,直接基于内存二进制流进行复制,不需要再经历耗时的对象初始化过程(不调用构造函数),能够提升对象复制的性能
- 当对象的构建过程比较耗时时,可以把当前系统中已存在的对象作为原型,对其进行复制(一般是基于二进制流的复制),躲避初始化过程,使得新对象的创建时间大大缩短
应用场景
原型模式主要适用于以下应用场景:
- 创建对象成本较大,需要优化资源,如:初始化时间长,占用CPU太多,或者占用网络资源太多等
- 创建一个对象需要烦琐的数据准备或访问权限等,需要提高性能或者提高安全性
- 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值
在 Spring 中,原型模式应用得非常广泛,例如 scope=”prototype”、JSON.parseObject(),都是原型模式的具体应用
原型模式主要包含 2 个角色:
- 抽象原型(IPrototype):规定复制接口
- 具体原型(ConcretePrototype):被复制的对象
注意:不是通过 new 关键字而是通过对象赋值来实现创建对象的模式被称作原型模式
public class PrototypeDemo {
public static void main(String[] args) {
// 创建原型对象
ConcretePrototype prototype = new ConcretePrototype("原型");
ConcretePrototype clone = prototype.clone();
System.out.println(clone);
}
// 抽象原型
interface IPrototype<T> {
T clone();
}
// 具体原型
static class ConcretePrototype implements IPrototype<ConcretePrototype> {
private String desc;
public ConcretePrototype(String desc) {
this.desc = desc;
}
@Override
public ConcretePrototype clone() {
return new ConcretePrototype(this.desc);
}
@Override
public String toString() {
return "ConcretePrototype{" +
"desc='" + desc + '\'' +
'}';
}
}
}
原型模式的应用
使用:在 java 中,无需手动创建原型接口,已经内置了 Cloneable 抽象原型接口,自定义的类型只需要实现该接口并重写 Object.clone() 方法即可完成本类的复制
Cloneable 接口是一个空接口,提供该接口的作用:
- 为了在运行时通知 java 虚拟机可以安全地该类上使用 clone() 方法
- 如果该类没有实现 Cloneable 接口,调用 clone() 方法会抛出 CloneNotSupportedException 异常
一般情况下,使用 clone() 需要满足以下条件:
- 对任何对象 o,都有 o.clone() != o,即克隆对象与原型对象不是同一个对象
- 对任何对象 o,都有 o.clone().getClass() == o.getClass(),即克隆对象与原型对象的类型一样
- 如果对象 o 的 equals() 方法定义恰当,则 o.clone().equals(o) 应当成立
在设计自定义类的 clone() 方法时,一般前 2 个方法是必需的,第 3 个是可选的
public class PrototypeWithCloneable {
public static void main(String[] args) {
ConcretePrototype prototype = new ConcretePrototype("测试");
ConcretePrototype clone = prototype.clone();
System.out.println(clone);
}
static class ConcretePrototype implements Cloneable {
private String desc;
public ConcretePrototype(String desc) {
this.desc = desc;
}
@Override
protected ConcretePrototype clone() {
ConcretePrototype cloneType = null;
try {
// super.clone() 方法直接从堆内存中以二进制流的方式进行复制,重新分配一个内存块,因此其效率很高
// super.clone() 基于内存复制,属于浅拷贝
cloneType = (ConcretePrototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return cloneType;
}
@Override
public String toString() {
return "ConcretePrototype{" +
"desc='" + desc + '\'' +
'}';
}
}
}
super.clone() 方法:
- 优点:
- 直接从堆内存中以二进制流的方式进行复制,重新分配一个内存块,因此其效率很高
- 由于 super.clone() 方法基于内存复制,因此不会调用对象的构造函数,也就是不需要经历初始化过程
- 缺点:
- 属于浅拷贝,如果类中存在引用对象属性,则原型对象与克隆对象的该属性会指向同一对象的引用
- 如果需要实现深拷贝,需要在对象创建之后再对其中的属性一一进行拷贝,特别麻烦,因此一般使用序列化实现深拷贝
使用序列化实现深拷贝
通过序列化实现深拷贝的注意事项:
- 被拷贝的对象必须实现 Serializable 接口,同时定义一个 private static final long serialVersionUID = 3760742309246179789L; 对象用于标识唯一的一个类,避免因为类的修改导致生成 serial VersionUID 不同而反序列化失败
同样,被拷贝的对象如果存在对象类型的属性,那么这些必须同样实现 Serializable 接口,否则会报 NotSerializableException 异常
/**
* 深拷贝方法,通过序列化和非序列实现,因此,需要深拷贝的对象必须实现Serializable接口,
* 且对象中如果存在对象引用,该对象引用所指向的对象也必须实现 Serializable,否则会抛出异常
*
* @param source 要深拷贝的对象
* @param <T> 泛型
* @return 拷贝的对象
*/
public static <T> T deepCopy(T source) throws IOException, ClassNotFoundException {
if (source instanceof Serializable) {
// ByteArrayOutStream指向内存中的一个字节数组,用于保存序列化后的对象
// 该流不需要进行关闭,因为当不需要使用时,GC会自动回收该对象
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 创建对象输出流
ObjectOutputStream oos = new ObjectOutputStream(bos);
// 写入对象
oos.writeObject(source);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (T) ois.readObject();
}
throw new NotSerializableException("被拷贝的对象必须实现Serializable接口!");
}
对深拷贝方法的测试:
public class DeepCopyDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException, CloneNotSupportedException {
// 通过序列化和反序列实现深拷贝
Demo demo = new Demo().setUser(new User().setName("李四").setAge(18));
Demo deepCopy = DeepCopyDemo.deepCopy(demo);
deepCopy.getUser().setAge(19);
// 浅拷贝
Demo clone = demo.clone();
clone.getUser().setName("王五");
System.out.println(demo);
System.out.println(deepCopy);
System.out.println(clone);
}
/**
* 深拷贝方法,通过序列化和非序列实现,因此,需要深拷贝的对象必须实现Serializable接口,
* 且对象中如果存在对象引用,该对象引用所指向的对象也必须实现 Serializable,否则会抛出异常
*
* @param source 要深拷贝的对象
* @param <T> 泛型
* @return 拷贝的对象
*/
public static <T> T deepCopy(T source) throws IOException, ClassNotFoundException {
if (source instanceof Serializable) {
// ByteArrayOutStream指向内存中的一个字节数组,用于保存序列化后的对象
// 该流不需要进行关闭,因为当不需要使用时,GC会自动回收该对象
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 创建对象输出流
ObjectOutputStream oos = new ObjectOutputStream(bos);
// 写入对象
oos.writeObject(source);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (T) ois.readObject();
}
throw new NotSerializableException("被拷贝的对象必须实现Serializable接口!");
}
@Data
@Accessors(chain = true)
static class User implements Serializable,Cloneable {
private static final long serialVersionUID = 3760742309246179789L;
private String name;
private Integer age;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Data
@Accessors(chain = true)
static class Demo implements Serializable, Cloneable {
private static final long serialVersionUID = -1497612712921169083L;
private User user;
@Override
protected Demo clone() throws CloneNotSupportedException {
return (Demo) super.clone();
}
}
}
上述输出结果为:
DeepCopyDemo.Demo(user=DeepCopyDemo.User(name=王五, age=18))
DeepCopyDemo.Demo(user=DeepCopyDemo.User(name=李四, age=19))
DeepCopyDemo.Demo(user=DeepCopyDemo.User(name=王五, age=18))
结论:
- 对于浅拷贝,只需要当前对象实现 Cloneable 接口,然后覆写 Object.clone() 方法即可,不要求对象中的对象属性也实现 Cloneable 接口
- 对于序列化实现的深拷贝,需求当前类和类中对象属性都要实现 Serializable 接口,同时最好显示声明 serialVersionUID 接口,避免因类的修改导致反序列化失败