原型模式
什么是原型模式?
原型模式(Prototype Pattern)是一种创建型设计模式,它允许一个对象通过复制现有对象(即原型)来创建新的对象,而不是通过实例化类。
这种模式适用于以下情况:
- 当创建新对象成本较高时,可以通过复制已有对象来节省开销。
- 当对象的状态需要频繁改变时,可以使用原型模式来创建新的状态。
原型模式构成
- 抽象原型类(或原型接口)(Prototype):声明一个克隆自身的方法。
- 具体原型类(ConcretePrototype):实现抽象原型类(接口)定义的克隆方法,提供一个具体的克隆方法来复制自己。
- 客户端(Client):通过调用原型接口的克隆方法来创建新的对象。
优缺点
- 优点
- 简化对象创建:通过克隆已有对象可以简化复杂对象的创建过程。
- 性能优化:减少创建新对象的开销,节省时间和资源
- 动态系统:在运行时刻动态地创建对象,而无需指定具体类。
- 保护原始对象:防止意外修改对原对象产生影响。
- 缺点
- 克隆限制:有些对象无法轻易克隆,比如包含文件句柄、网络连接等资源的对象。
- 复杂性:需要实现对象的深拷贝或浅拷贝,会增加代码的复杂性。
知识点回顾
由于原型模式就是复制现有对象,创建新对象,由复制引出浅拷贝和深拷贝的概念。其实核心并不在原型模式,而是拷贝对象,只有明白了复制过程中对象和属性在jvm内存中的具体分配
情况,才能明白为什么有浅拷贝和深拷贝和他们的原理,先回顾一下java中的以下知识点
【系统全面详细的学习jvm知识,推荐去尚硅谷看康师傅的视频课程】:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机)
基本类型和引用类型
- 基本类型 也称为值类型包括
- 整数类型:byte、short、int、long
- 浮点类型:float、double
- 字符串类型:char
- 布尔类型:boolean
- 引用类型
- Class(类)
- Interface(接口)
- Array(数组)
jvm内存模型
- 内存模型图:
1. 虚拟机栈
- 主管Java程序的运行
- 每个线程都有自己的栈,栈中的数据都是以栈帧(Stack Frame)的格式存在
- 栈的操作遵循“先进后出”/“后进先出”原则,就是对栈帧的压栈和出栈
- 栈帧内部结构包括
- 局部变量表(Local Variables)
- 操作数栈(operand Stack)(或表达式栈)
- 动态链接(DynamicLinking)(或指向运行时常量池的方法引用)
- 方法返回地址(Return Address)(或方法正常退出或者异常退出的定义)
- 一些附加信息
- 结构图
2. 堆
- 主要用于存储实例化的对象,数组。
- 堆也是Java内存管理的核心区域,是GC执行垃圾回收的重点区域。
- 堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据
- 堆的内部结构包括:
- Java 7及之前:新生区(年轻代/新生代)+养老区(老年代)+永久区
- Java 8及之后:新生区(年轻代/新生代)+养老区(老年代)+元空间
- 通过选项"-Xmx"和"-Xms"来进行设置堆的大小
- 结构图
3. 方法区
- 方法区是一块独立于Java堆的内存空间。
- 是各个线程共享的内存区域,在JVM启动的时候被创建
- 方法区是主要存储
- 类信息:类型的完整有效名称(全名=包名.类名),直接父类的完整有效名,类型的修饰符
- 方法信息:方法名称,返回类型(或void),修饰符(public,private,protected…)
- 静态变量:static修饰的变量
- 运行时常量池:方法区的一部分,将编译期的Class文件中的字面量与符号引用放入运行时常量池,并把符号引用转为直接引用
- 栈、堆、方法区的交互关系图
4. 对象实例化的内存工作分配图
- 代码示例
public class Customer{
int id = 1001;
String name;
Account acct;
{
name = "匿名客户";
}
public Customer() {
acct = new Account();
}
}
public class CustomerTest{
public static void main(string[] args){
Customer cust=new Customer();
}
}
- 内存分配图
对上面的知识点了解后,那么接下来的理解就很容易了,下面我们通过一个生活中具体的案例来讲解对象的复制,浅拷贝,深拷贝。
场景案例
在日常运维工作中,每周都要统计各个服务的运行报告,这些报告的结构和格式基本相同,只有报告编号和报告名称有所不同,并且每份报告都包含一个支撑团队,看代码。
类图如下:
代码如下:
- 服务报告
import lombok.Data;
/**
* @Author: javafa
* @Date: 2024/7/17 15:00
* @Description: 服务报告类
*/
@Data
public class ServiceReport{
/**
* 报告编号
*/
private int id;
//报告名称
private String reportName;
//新增支撑团队
private SupportTeam supportTeam;
}
- 支撑团队
import lombok.Data;
/**
* @Author: javafa
* @Date: 2024/7/17 15:42
* @Description: 支撑团队
*/
@Data
public class SupportTeam {
//团队编号
private int teamNo;
//团队名称
private String teamName;
}
- 调用测试
@Test
public void initTest(){
//定义数据采集团队
SupportTeam supportTeam = new SupportTeam();
supportTeam.setTeamNo(100);
supportTeam.setTeamName("数据采集团队");
//创建原型服务报告
ServiceReport serviceReport = new ServiceReport();
serviceReport.setId(202401);
serviceReport.setReportName("数据采集服务");
serviceReport.setSupportTeam(supportTeam);
System.out.println("serviceReport:"+serviceReport);
System.out.println("-----------------------------------------");
System.out.println("原对象serviceReport内存地址值:"+System.identityHashCode(serviceReport));
System.out.println("原对象supportTeam内存地址值:"+System.identityHashCode(supportTeam));
System.out.println("原对象serviceReport的supportTeam对象属性内存地址值:"+System.identityHashCode(serviceReport.getSupportTeam()));
System.out.println("-----------------------------------------");
System.out.println("原对象reportName常量池地址:"+System.identityHashCode(serviceReport.getReportName()));
System.out.println("原对象teamName常量池地址:"+System.identityHashCode(supportTeam.getTeamName()));
}
- 测试结果
内存分配图
- 分配解释:
- 我们通过SupportTeam supportTeam = new SupportTeam(); 创建了一个对象,在栈中分配一个名为supportTeam,只保存该对象在堆中的内存地址为1582071873。
- 在堆中分配SupportTeam类的对象实例,包含了基本数据类型为int的teamNo属性值为100,teamName属性值为“数据采集团队”,由于teamName类型为String类型,
String类型是虽然引用类型,但其字面量的值“数据采集团队” 会被放入常量池,内存地址为:433287555。 - 栈中的supportTeam属性指向堆内存中的SupportTeam对象,其内存地址为1582071873。
- 通过ServiceReport serviceReport = new ServiceReport(); 在栈中分配一个名为serviceReport的对象,只保存该对象在堆中的内存地址为2084663827。
- 在堆中分配ServiceReport类的对象实例,包含了基本数据类型为int的id属性值为202401,reportName属性值为“数据采集服务”,由于reportName类型为String类型,
所以“数据采集服务” 也会被放入常量池,内存地址为:1908981452。supportTeam属性为对象类型,是一个引用类型,所以会保存一个指向堆内存中的SupportTeam对象的内存地址
其内存地址为就是步骤3中的1582071873。
对象复制
- 先来看我们最熟悉的对象复制是怎么个事儿,我们将serviceReport 复制一份得到serviceReport2
/**
* @Author: javafa
* @Description: 对象复制调用测试
* @Date: 2024/7/18 13:21
* @Param:
* @return: void
**/
@Test
public void copyTest(){
//定义数据采集团队
SupportTeam supportTeam = new SupportTeam();
supportTeam.setTeamNo(100);
supportTeam.setTeamName("数据采集团队");
//创建原型服务报告
ServiceReport serviceReport = new ServiceReport();
serviceReport.setId(202401);
serviceReport.setReportName("数据采集服务");
serviceReport.setSupportTeam(supportTeam);
//复制对象
ServiceReport serviceReport2 = serviceReport;
System.out.println("serviceReport:"+serviceReport);
System.out.println("serviceReport2:"+serviceReport2);
System.out.println("-----------------------------------------");
System.out.println("原对象serviceReport内存地址值:"+System.identityHashCode(serviceReport));
System.out.println("复制对象serviceReport2内存地址值:"+System.identityHashCode(serviceReport2));
System.out.println("-----------------------------------------");
System.out.println("原对象supportTeam内存地址值:"+System.identityHashCode(supportTeam));
System.out.println("原对象serviceReport的supportTeam对象属性内存地址值:"+System.identityHashCode(serviceReport.getSupportTeam()));
System.out.println("复制对象serviceReport2的supportTeam对象属性内存地址值:"+System.identityHashCode(serviceReport2.getSupportTeam()));
System.out.println("-----------------------------------------");
System.out.println("原对象reportName常量池地址:"+System.identityHashCode(serviceReport.getReportName()));
System.out.println("复制对象serviceReport2的reportName常量池地址:"+System.identityHashCode(serviceReport2.getReportName()));
System.out.println("原对象teamName常量池地址:"+System.identityHashCode(serviceReport.getSupportTeam().getTeamName()));
System.out.println("复制对象serviceReport2的teamName常量池地址:"+System.identityHashCode(serviceReport2.getSupportTeam().getTeamName()));
}
- 测试结果
内存分配图
- 分配解释:
- 我们通过 ServiceReport serviceReport2 = serviceReport; 将serviceReport对象复制一份给serviceReport2后,
serviceReport2对象在栈中分配一个名为serviceReport2的对象,而内存地址仍然指向堆内存中的ServiceReport对象,其内存地址为2084663827。 - 此时堆内存中只有一份ServiceReport对象,复制后serviceReport2对象只在栈中生成了一份serviceReport2,
引用地址同样指向堆内存地址为2084663827中的ServiceReport对象。
浅拷贝
定义:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。在Java中,可以通过调用Object类的clone()方法实现浅拷贝,
clone()方法默认实现是浅拷贝,即复制对象本身,复制对象的成员变量,复制引用类型的成员变量指向的该对象的内存地址。
- 服务报告 ——> 实现Cloneable接口
import lombok.Data;
/**
* @Author: javafa
* @Date: 2024/7/17 15:00
* @Description: 服务报告类
*/
@Data
public class ServiceReport implements Cloneable {
/**
* 报告编号
*/
private int id;
//报告名称
private String reportName;
//新增支撑团队
private SupportTeam supportTeam;
/**
* @Author: javafa
* @Description: 重写 clone 方法
* @Date: 2024/7/18 15:42
* @Param:
* @return: com.fivemillion.algorithm.designpatterns.prototype.ServiceReport
**/
@Override
public ServiceReport clone() {
try {
return (ServiceReport) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
- 支撑团队 ——> 实现Cloneable接口
import lombok.Data;
/**
* @Author: javafa
* @Date: 2024/7/17 15:42
* @Description: 支撑团队
*/
@Data
public class SupportTeam implements Cloneable {
//团队编号
private int teamNo;
//团队名称
private String teamName;
/**
* @Author: javafa
* @Description: 重写 clone 方法
* @Date: 2024/7/18 15:43
* @Param:
* @return: com.fivemillion.algorithm.designpatterns.prototype.SupportTeam
**/
@Override
public SupportTeam clone() {
try {
return (SupportTeam) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
- 调用测试
/**
* @Author: javafa
* @Description: 浅拷贝调用测试
* @Date: 2024/7/18 16:03
* @Param:
* @return: void
**/
@Test
public void shallowCloneTest(){
//定义数据采集团队
SupportTeam supportTeam = new SupportTeam();
supportTeam.setTeamNo(100);
supportTeam.setTeamName("数据采集团队");
//创建原型服务报告
ServiceReport serviceReport = new ServiceReport();
serviceReport.setId(202401);
serviceReport.setReportName("数据采集服务");
serviceReport.setSupportTeam(supportTeam);
//浅拷贝获得对象
ServiceReport shallowCloneServiceReport = serviceReport.clone();
System.out.println("serviceReport:"+serviceReport);
System.out.println("shallowCloneServiceReport:"+shallowCloneServiceReport);
System.out.println("-----------------------------------------");
System.out.println("原对象serviceReport内存地址值:"+System.identityHashCode(serviceReport));
System.out.println("浅拷贝对象shallowCloneServiceReport内存地址值:"+System.identityHashCode(shallowCloneServiceReport));
System.out.println("-----------------------------------------");
System.out.println("原对象supportTeam内存地址值:"+System.identityHashCode(supportTeam));
System.out.println("原对象serviceReport的supportTeam对象属性内存地址值:"+System.identityHashCode(serviceReport.getSupportTeam()));
System.out.println("浅拷贝对象shallowCloneServiceReport的supportTeam对象属性内存地址值:"+System.identityHashCode(shallowCloneServiceReport.getSupportTeam()));
System.out.println("-----------------------------------------");
System.out.println("原对象reportName常量池地址:"+System.identityHashCode(serviceReport.getReportName()));
System.out.println("浅拷贝对象shallowCloneServiceReport的reportName常量池地址:"+System.identityHashCode(shallowCloneServiceReport.getReportName()));
System.out.println("原对象teamName常量池地址:"+System.identityHashCode(serviceReport.getSupportTeam().getTeamName()));
System.out.println("浅拷贝对象shallowCloneServiceReport的teamName常量池地址:"+System.identityHashCode(shallowCloneServiceReport.getSupportTeam().getTeamName()));
}
- 测试结果
内存分配图
- 分配解释:
-
我们通过 ServiceReport shallowCloneServiceReport = serviceReport.clone(); 将serviceReport对象浅拷贝一份
得到shallowCloneServiceReport,此时栈中会分配一个名为shallowCloneServiceReport的对象,保存堆内存中的地址为1582071873,
注意此时和原对象serviceReport内存地址值:2084663827是不同的,这也区别于复制的对象,因为
复制时栈中的serviceReport 和 serviceReport2 对象在栈中的地址是相同的。 -
此时堆内存中拷贝了一份ServiceReport对象,注意通过浅拷贝后,堆内存中存在了一份新的ServiceReport对象,其地址为1582071873,这个堆对象是独立的新对象
- 引用类型成员变量reportName的内存地址为433287555,这和原对象serviceReport的reportName内存地址值:433287555是相同的,
都仍然指向常量池中的字符串"数据采集服务", - 引用类型的成员变量supportTeam对象地址为1908981452,这和原对象serviceReport的supportTeam对象地址值:1908981452是相同的,
都仍然指向堆内存中同一个SupportTeam对象。 - 基本类型的成员变量id值为202401
- 引用类型成员变量reportName的内存地址为433287555,这和原对象serviceReport的reportName内存地址值:433287555是相同的,
-
接下来我们尝试修改shallowCloneServiceReport的id,reportName,teamName的值,看看是否会改变原对象serviceReport的值,来证明浅拷贝中哪些属性
是被完全独立拷贝了一份,哪些只是拷贝了地址引用。
修改shallowCloneServiceReport的id,reportName,teamName的值,如下:
/**
* @Author: javafa
* @Description: 浅拷贝后的对象属性修改调用测试
* @Date: 2024/7/18 16:03
* @Param:
* @return: void
**/
@Test
public void shallowCloneUpTest() {
//定义数据采集团队
SupportTeam supportTeam = new SupportTeam();
supportTeam.setTeamNo(100);
supportTeam.setTeamName("数据采集团队");
//创建原型服务报告
ServiceReport serviceReport = new ServiceReport();
serviceReport.setId(202401);
serviceReport.setReportName("数据采集服务");
serviceReport.setSupportTeam(supportTeam);
//浅拷贝获得对象并修改属性值
ServiceReport shallowCloneServiceReport = serviceReport.clone();
shallowCloneServiceReport.setId(202402);//修改报告编号
shallowCloneServiceReport.setReportName("数据分析服务");//修改报告名称
shallowCloneServiceReport.getSupportTeam().setTeamNo(101);//修改团队编号
shallowCloneServiceReport.getSupportTeam().setTeamName("数据分析团队");//修改团队名称
System.out.println("serviceReport:" + serviceReport);
System.out.println("shallowCloneServiceReport:" + shallowCloneServiceReport);
System.out.println("-----------------------------------------");
System.out.println("原对象serviceReport内存地址值:" + System.identityHashCode(serviceReport));
System.out.println("浅拷贝对象shallowCloneServiceReport内存地址值:" + System.identityHashCode(shallowCloneServiceReport));
System.out.println("-----------------------------------------");
System.out.println("原对象supportTeam内存地址值:" + System.identityHashCode(supportTeam));
System.out.println("原对象serviceReport的supportTeam对象属性内存地址值:" + System.identityHashCode(serviceReport.getSupportTeam()));
System.out.println("浅拷贝对象shallowCloneServiceReport的supportTeam对象属性内存地址值:" + System.identityHashCode(shallowCloneServiceReport.getSupportTeam()));
System.out.println("-----------------------------------------");
System.out.println("原对象reportName常量池地址:" + System.identityHashCode(serviceReport.getReportName()));
System.out.println("浅拷贝对象shallowCloneServiceReport的reportName常量池地址:" + System.identityHashCode(shallowCloneServiceReport.getReportName()));
System.out.println("原对象teamName常量池地址:" + System.identityHashCode(serviceReport.getSupportTeam().getTeamName()));
System.out.println("浅拷贝对象shallowCloneServiceReport的teamName常量池地址:" + System.identityHashCode(shallowCloneServiceReport.getSupportTeam().getTeamName()));
}
- 测试结果
内存分配图
为了对比浅拷贝修改前的内存分配图,我们将他们放在一起比较
- 分配解释:
- 通过观察测试结果serviceReport和shallowCloneServiceReport对象json数据,会发现出现了一些很有意思的事儿
- 我们通过浅拷贝对象shallowCloneServiceReport.setId(202402);//修改报告编号,发现原对象serviceReport的id值没有变化仍然为202401,
这表明基本数据类型int的属性被完全拷贝了一份,基本数据类型属性是完全独立的,不会影响原对象。 - 通过浅拷贝对象shallowCloneServiceReport.setReportName(“数据分析服务”);//修改报告名称,发现原对象serviceReport的reportName值没有变化仍然为"数据采集服务",
这里就诡异了,不是说String类型是引用类型,引用类型拷贝的是地址嘛,那为什么修改了shallowCloneServiceReport的reportName值内存地址变为27319466,
原对象serviceReport的reportName地址还是433287555值没有变化呢?
因为 String的值被放入了常量池中,当我们重新设置reportName属性时,JVM会先检查常量池中是否有相同的值,如果有相同的值,就会直接使用常量池中的值,返回常量池地址,
如果常量池中没有则会将新的值放入常量池中,并返回常量池地址。所以“数据采集服务”常量池地址为433287555,“数据分析服务”常量池地址为27319466。 - 通过浅拷贝对象shallowCloneServiceReport.getSupportTeam().setTeamNo(101);//修改团队编号,发现原对象serviceReport的supportTeam的teamNo值由100也变为101,
通过浅拷贝对象shallowCloneServiceReport.getSupportTeam().setTeamName(“数据分析团队”);//修改团队名称,发现原对象serviceReport的supportTeam的teamName值由"数据采集团队"变为"数据分析团队",
这说明原对象serviceReport和浅拷贝对象shallowCloneServiceReport指向的是同一份supportTeam对象,即他们的引用地址是相同的,当supportTeam发生变动时,
他们都会受到影响。
深拷贝
有一天,领导觉着这份报告生成的实在是牛哇,想要所有产线团队推广,这时我们就得完全复制一份后,供他们自己修改内容,这时每个产线的所有修改都应该时独立
互不影响生成自己的报告,所以我们得进行深拷贝。
代码如下:
- 服务报告
import lombok.Data;
/**
* @Author: javafa
* @Date: 2024/7/17 15:00
* @Description: 服务报告类
*/
@Data
public class ServiceReport implements Cloneable {
/**
* 报告编号
*/
private int id;
//报告名称
private String reportName;
//新增支撑团队
private SupportTeam supportTeam;
/**
* @Author: javafa
* @Description: 重写 clone 方法
* @Date: 2024/7/18 15:42
* @Param:
* @return: com.fivemillion.algorithm.designpatterns.prototype.ServiceReport
**/
@Override
public ServiceReport clone() {
try {
return (ServiceReport) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
/**
* @Author: javafa
* @Description: 深度拷贝
* @Date: 2024/7/18 17:46
* @Param:
* @return: com.fivemillion.algorithm.designpatterns.prototype.ServiceReport
**/
public ServiceReport deepClone() {
try {
ServiceReport cloned = (ServiceReport) super.clone();
cloned.setSupportTeam(this.supportTeam.clone());
return cloned;
} catch (CloneNotSupportedException e) {
throw new RuntimeException("Clone not supported", e);
}
}
}
- 调用测试
/**
* @Author: javafa
* @Description: 深拷贝后的对象属性修改调用测试
* @Date: 2024/7/18 17:54
* @Param:
* @return: void
**/
@Test
public void deepCloneTest() {
//定义数据采集团队
SupportTeam supportTeam = new SupportTeam();
supportTeam.setTeamNo(100);
supportTeam.setTeamName("数据采集团队");
//创建原型服务报告
ServiceReport serviceReport = new ServiceReport();
serviceReport.setId(202401);
serviceReport.setReportName("数据采集服务");
serviceReport.setSupportTeam(supportTeam);
//深拷贝获得对象并修改属性值
ServiceReport deepCloneServiceReport = serviceReport.deepClone();
deepCloneServiceReport.setId(202402);//修改报告编号
deepCloneServiceReport.setReportName("数据分析服务");//修改报告名称
deepCloneServiceReport.getSupportTeam().setTeamNo(101);//修改团队编号
deepCloneServiceReport.getSupportTeam().setTeamName("数据分析团队");//修改团队名称
System.out.println("serviceReport:" + serviceReport);
System.out.println("deepCloneServiceReport:" + deepCloneServiceReport);
System.out.println("-----------------------------------------");
System.out.println("原对象serviceReport内存地址值:" + System.identityHashCode(serviceReport));
System.out.println("深拷贝对象deepCloneServiceReport内存地址值:" + System.identityHashCode(deepCloneServiceReport));
System.out.println("-----------------------------------------");
System.out.println("原对象supportTeam内存地址值:" + System.identityHashCode(supportTeam));
System.out.println("原对象serviceReport的supportTeam对象属性内存地址值:" + System.identityHashCode(serviceReport.getSupportTeam()));
System.out.println("深拷贝对象deepCloneServiceReport的supportTeam对象属性内存地址值:" + System.identityHashCode(deepCloneServiceReport.getSupportTeam()));
System.out.println("-----------------------------------------");
System.out.println("原对象reportName常量池地址:" + System.identityHashCode(serviceReport.getReportName()));
System.out.println("深拷贝对象deepCloneServiceReport的reportName常量池地址:" + System.identityHashCode(deepCloneServiceReport.getReportName()));
System.out.println("原对象teamName常量池地址:" + System.identityHashCode(serviceReport.getSupportTeam().getTeamName()));
System.out.println("深拷贝对象deepCloneServiceReport的teamName常量池地址:" + System.identityHashCode(deepCloneServiceReport.getSupportTeam().getTeamName()));
}
- 测试结果
内存分配图
- 分配解释:
- 通过深拷贝获得deepCloneServiceReport对象,为当前对象重新赋值后, 通过测试结果中对象的json输出可以发现,两个对象的属性值各自独立不相同
- 通过原对象进行深拷贝获得deepCloneServiceReport对象, 首先在栈中创建一个名为deepCloneServiceReport的对象,保存指向堆内存的地址为:1582071873,
在堆中完全创建一份ServiceReport对象,包括其属性引用类型的String变量reportName地址为1003752023指向常量池中“数据分析服务”,引用类型的对象supportTeam
地址为:43287555指向堆中完全创建一份的SupportTeam对象 - 在栈中通过supportTeam对象创建一个新的地址为433287555的指向对内存的对象,在堆内存中完全创建一份SupportTeam的对象,包括其属性
为引用类型的String变量teamName,地址为226744878 - 通过测试结果和内存分配图可以清晰看到,在深拷贝模式下,创建出的对象包括对象的属性无论是基本类型还是引用类型,其内存地址和属性值都完全不同于原对象,
都完全会独立一份存在互不影响。
原型模式和传统对象赋值的性能测试对比
文章开头说到原型模式的优点时提到,“性能优化:减少创建新对象的开销,节省时间和资源”,接下来通过程序示例来观察实际如何,为了更加明显的看到耗时情况,我们对实体类进行
简单的修改
- 将ServiceReport 报告类的属性supportTeam修改为集合类型,以便添加更多的支撑团队对象
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: javafa
* @Date: 2024/7/17 15:00
* @Description: 服务报告类
*/
@Data
public class ServiceReportTmp implements Cloneable {
/**
* 报告编号
*/
private int id;
//报告名称
private String reportName;
//新增支撑团队对象集合
private List<SupportTeamTmp> supportTeams;
/**
* @Author: javafa
* @Description: 重写 clone 方法
* @Date: 2024/7/18 15:42
* @Param:
* @return: com.fivemillion.algorithm.designpatterns.prototype.ServiceReport
**/
@Override
public ServiceReportTmp clone() {
try {
return (ServiceReportTmp) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
/**
* @Author: javafa
* @Description: 深度拷贝
* @Date: 2024/7/18 17:46
* @Param:
* @return: com.fivemillion.algorithm.designpatterns.prototype.ServiceReport
**/
public ServiceReportTmp deepClone() {
try {
ServiceReportTmp cloned = (ServiceReportTmp) super.clone();
List<SupportTeamTmp> clonedSupportTeams = new ArrayList<>();
for (SupportTeamTmp team : this.supportTeams) {
clonedSupportTeams.add(team.clone());
}
cloned.setSupportTeams(clonedSupportTeams);
return cloned;
} catch (CloneNotSupportedException e) {
throw new RuntimeException("Clone not supported", e);
}
}
}
- 为SupportTeamTmp 支撑团队增加 Member 对象,并定义为集合类型,增加嵌套对象达到复杂性的效果
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: javafa
* @Date: 2024/7/17 15:42
* @Description: 支撑团队
*/
@Data
public class SupportTeamTmp implements Cloneable {
//团队编号
private int teamNo;
//团队名称
private String teamName;
//增加成员对象
private List<Member> members;
/**
* @Author: javafa
* @Description: 重写 clone 方法
* @Date: 2024/7/18 15:43
* @Param:
* @return: com.fivemillion.algorithm.designpatterns.prototype.SupportTeam
**/
@Override
public SupportTeamTmp clone() {
try {
SupportTeamTmp cloned = (SupportTeamTmp) super.clone();
List<Member> clonedMembers = new ArrayList<>();
for (Member member : this.members) {
clonedMembers.add(member.clone());
}
cloned.setMembers(clonedMembers);
return cloned;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
- Member成员类
import lombok.Data;
/**
* @Author: javafa
* @Date: 2024/7/19 10:39
* @Description: 成员类
*/
@Data
public class Member implements Cloneable{
private int memberId;
private String memberName;
/**
* 重写clone方法实现浅拷贝
* @return
*/
@Override
protected Member clone() {
try {
return (Member) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
- 调用测试
import com.fivemillion.algorithm.designpatterns.prototype.Member;
import com.fivemillion.algorithm.designpatterns.prototype.ServiceReportTmp;
import com.fivemillion.algorithm.designpatterns.prototype.SupportTeamTmp;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: javafa
* @Date: 2024/7/17 15:06
* @Description: 原型模式测试
*/
public class PrototypeTimeTest {
/**
* @Author: javafa
* @Description: 对象拷贝耗时调用测试
* @Date: 2024/7/19 12:26
* @Param:
* @return: void
**/
@Test
public void copyTimeTest() {
// 测试对象生成次数
int iterations = 100000;
//准备对象和属性数据
ServiceReportTmp originalReport = initData();
for(int i = 0 ;i<3;i++){
//传统对象拷贝耗时测试
traditionCopyTimeTest(iterations, originalReport);
//浅拷贝对象耗时测试
//shallowCloneTimeTest(iterations, originalReport);
//深拷贝对象耗时测试
//deepCloneTimeTest(iterations, originalReport);
}
}
/**
* @Author: javafa
* @Description: 准备对象数据
* @Date: 2024/7/19 10:57
* @Param:
* @return: com.fivemillion.algorithm.designpatterns.prototype.ServiceReportTmp
**/
public ServiceReportTmp initData() {
//准备成员对象集合
List<Member> members = new ArrayList<>();
for (int i = 0; i < 100; i++) {
Member member = new Member();
member.setMemberId(i);
member.setMemberName("Member " + i);
members.add(member);
}
//准备支撑团队对象集合
List<SupportTeamTmp> supportTeams = new ArrayList<>();
for (int i = 0; i < 100; i++) {
SupportTeamTmp supportTeam = new SupportTeamTmp();
supportTeam.setTeamNo(i);
supportTeam.setTeamName("Team " + i);
supportTeam.setMembers(members);
supportTeams.add(supportTeam);
}
//准备报告对象
ServiceReportTmp originalReport = new ServiceReportTmp();
originalReport.setId(101);
originalReport.setReportName("Daily Report");
originalReport.setSupportTeams(supportTeams);
return originalReport;
}
/**
* @Author: javafa
* @Description: 传统对象拷贝的具体实现方法
* @Date: 2024/7/19 10:58
* @Param: original
* @return: com.fivemillion.algorithm.designpatterns.prototype.ServiceReportTmp
**/
public static ServiceReportTmp manualCopy(ServiceReportTmp original) {
ServiceReportTmp copyReport = new ServiceReportTmp();
copyReport.setId(original.getId());
copyReport.setReportName(original.getReportName());
List<SupportTeamTmp> originalTeams = original.getSupportTeams();
List<SupportTeamTmp> copyTeams = new ArrayList<>();
for (SupportTeamTmp team : originalTeams) {
SupportTeamTmp copyTeam = new SupportTeamTmp();
copyTeam.setTeamNo(team.getTeamNo());
copyTeam.setTeamName(team.getTeamName());
List<Member> originalMembers = team.getMembers();
List<Member> copyMembers = new ArrayList<>();
for (Member member : originalMembers) {
Member copyMember = new Member();
copyMember.setMemberId(member.getMemberId());
copyMember.setMemberName(member.getMemberName());
copyMembers.add(copyMember);
}
copyTeam.setMembers(copyMembers);
copyTeams.add(copyTeam);
}
copyReport.setSupportTeams(copyTeams);
return copyReport;
}
/**
* @Author: javafa
* @Description: 传统拷贝对象耗时测试
* @Date: 2024/7/19 10:57
* @Param: iterations
* @Param: originalReport
* @return: void
**/
private static void traditionCopyTimeTest(int iterations, ServiceReportTmp originalReport) {
// 手动复制测试
long manualStartTime = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
ServiceReportTmp copy = manualCopy(originalReport);
}
long manualEndTime = System.currentTimeMillis();
long manualDuration = manualEndTime - manualStartTime;
System.out.println("手动复制耗时: " + manualDuration + " 毫秒");
}
/**
* @Author: javafa
* @Description: 深拷贝对象耗时
* @Date: 2024/7/19 10:57
* @Param: iterations
* @Param: originalReport
* @return: void
**/
private static void deepCloneTimeTest(int iterations, ServiceReportTmp originalReport) {
// 深拷贝测试
long deepStartTime = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
ServiceReportTmp copy = originalReport.deepClone();
}
long deepEndTime = System.currentTimeMillis();
long deepDuration = deepEndTime - deepStartTime;
System.out.println("深拷贝耗时: " + deepDuration + " 毫秒");
}
/**
* @Author: javafa
* @Description: 浅拷贝对象耗时
* @Date: 2024/7/19 10:58
* @Param: iterations
* @Param: originalReport
* @return: void
**/
private static void shallowCloneTimeTest(int iterations, ServiceReportTmp originalReport) {
// 浅拷贝测试
long shallowStartTime = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
ServiceReportTmp copy = originalReport.clone();
}
long shallowEndTime = System.currentTimeMillis();
long shallowDuration = shallowEndTime - shallowStartTime;
System.out.println("浅拷贝耗时: " + shallowDuration + " 毫秒");
}
}
- 测试结果
为了尽量避免误差,我们每种拷贝都执行了3次,生成10万次报告对象,测试结果发现耗时 传统复制>深拷贝>浅拷贝
- 传统对象复制测试耗时
- 浅拷贝测试耗时
- 深拷贝测试耗时