一、什么是原型模式
原型模式是一种高效的对象复制技术,它通过复制已有对象的状态来快速生成新对象,避免了复杂的创建过程。以馒头坊为例,我们可以将原型模式比作使用标准化的馒头模具来制作馒头。每当需要制作新的馒头时,我们只需将面团填入模具,压模后取出,就能得到大小和形状完全一致的馒头,而无需从头开始调整面团的大小和形状。这样,馒头模具就像是一个“原型对象”,确保了每次复制的结果都是标准化和一致的,大大简化了制作过程。
二、为什么使用原型模式
我认为有两点:
(1)简化对象创建过程:原型模式可以简化对象创建的过程,可以直接拷贝现有的原型实例的值,实现对象复用。
(2)减少系统资源消耗:原型模式是在内存中进行二进制流的拷贝,要比直接new一个对象性能好,特别是在一个循环体内创建大量对象时。
在JAVA中原型模式主要有2种实现方式:(1)浅拷贝(2)深拷贝
三、原型模式示例
3.1 对象创建对比
使用new
关键字创建对象涉及以下步骤:
-
类加载检查:
-
JVM首先检查常量池,查找该类的符号引用,确认类是否已被加载、解析和初始化。
-
如果类尚未加载,JVM将执行类加载过程,包括验证、准备和解析步骤。
-
-
内存分配:
-
类加载完成后,JVM在堆内存中为新对象分配所需内存。
-
这个过程涉及到为对象的实例变量分配空间,并初始化为默认零值。
-
-
对象头设置:
-
分配内存后,JVM设置对象头,包含对象的元数据信息。
-
-
构造方法执行:
-
从JVM的视角看,对象已经创建。但从程序的角度,对象的创建还未结束,因为需要执行构造方法
<init>
来完成对象的初始化。 -
构造方法负责设置对象的初始状态,使其可以被程序使用。
-
相比之下,原型模式提供了一种不同的对象创建方式:
-
原型模式克隆:
-
在原型模式中,对象的创建不通过
new
关键字和构造方法,而是通过复制一个已存在的对象(原型)来实现。 -
这个过程直接在堆内存中进行,以二进制流的形式复制对象,为克隆对象重新分配内存空间。
-
由于跳过了构造方法的执行,原型模式避免了重复的构造调用,从而提高了对象创建的效率,特别是在创建复杂对象或需要频繁创建对象的场景中。
-
简而言之,使用new
关键字创建对象时,JVM遵循一系列步骤来初始化对象,而原型模式则通过复制现有对象来快速创建新对象,省略了构造过程。这种方式在需要快速复制对象时更为高效。
3.2 实现注意事项
(1)原型核心是重写Object中的clone方法,调用该方法可以在内存中进行对象拷贝。
(2)Java提供了一个标记接口——Cloneable,实现该接口完成标记,在JVM中具有这个标记的对象才有可能被拷贝。如果不实现该接口,克隆对象会抛出CloneNotSupportedException异常。
class ManTou implements Cloneable{
private Long num;
@Override
public ManTou clone(){
ManTou manTou = null;
try {
manTou = (ManTou)super.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return manTou;
}
}
3.3 实现方式
3.3.1 浅拷贝
当使用原型模式克隆对象时,对象的属性复制行为取决于属性的数据类型:
-
基本数据类型和不可变对象:
-
对于基本数据类型(如
int
、long
)和不可变的对象(如String
、基本类型的包装类),克隆操作会直接复制这些值到新对象中。 -
这意味着新对象和原对象在这些属性上拥有完全独立的副本,对这些属性的修改不会影响到另一个对象。
-
-
引用数据类型:
-
对于引用数据类型的属性,如对象的引用,克隆操作实际上复制的是引用本身,而不是引用所指向的对象。
-
这导致原对象和克隆对象的这些属性引用同一个实际对象,因此它们共享相同的内存地址。
-
任何一方对共享对象的修改都会反映在另一方,因为它们指向的是同一个实例。
-
class ManTou implements Cloneable{
private Long num;
@Override
public ManTou clone(){
ManTou manTou = null;
try {
manTou = (ManTou)super.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return manTou;
}
}
3.3.2 深拷贝
不管原型对象属性是简单数据类型还是引用对象类型都会完全的复制一份到新的对象中。两个对象之间互不影响
class ManTou implements Serializable,Cloneable{
private Long num;
@Override
public ManTou clone(){
ManTou manTou = null;
try {
ByteArrayOutputStream bas = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream()) {
oos.writeObject(this);
}
ByteArrayInputStream bis = new ByteArrayInputStream();
try (ObjectInputStream ois = new ObjectInputStream()) {
manTou = (ManTou) ois.readObject();
}
}catch (Exception e){
e.printStackTrace();
}
return manTou;
}
}
实现了深拷贝的相似工具包:(1)SerializationUtils(2) BeanUtils