设计模式-原型模式的完整代码示例及测试验证

原型模式

什么是原型模式?

原型模式(Prototype Pattern)是一种创建型设计模式,它允许一个对象通过复制现有对象(即原型)来创建新的对象,而不是通过实例化类。
这种模式适用于以下情况:

  1. 当创建新对象成本较高时,可以通过复制已有对象来节省开销。
  2. 当对象的状态需要频繁改变时,可以使用原型模式来创建新的状态。

原型模式构成

  1. 抽象原型类(或原型接口)(Prototype):声明一个克隆自身的方法。
  2. 具体原型类(ConcretePrototype):实现抽象原型类(接口)定义的克隆方法,提供一个具体的克隆方法来复制自己。
  3. 客户端(Client):通过调用原型接口的克隆方法来创建新的对象。

优缺点

  • 优点
  1. 简化对象创建:通过克隆已有对象可以简化复杂对象的创建过程。
  2. 性能优化:减少创建新对象的开销,节省时间和资源
  3. 动态系统:在运行时刻动态地创建对象,而无需指定具体类。
  4. 保护原始对象:防止意外修改对原对象产生影响。
  • 缺点
  1. 克隆限制:有些对象无法轻易克隆,比如包含文件句柄、网络连接等资源的对象。
  2. 复杂性:需要实现对象的深拷贝或浅拷贝,会增加代码的复杂性。

知识点回顾

由于原型模式就是复制现有对象,创建新对象,由复制引出浅拷贝和深拷贝的概念。其实核心并不在原型模式,而是拷贝对象,只有明白了复制过程中对象和属性在jvm内存中的具体分配
情况,才能明白为什么有浅拷贝和深拷贝和他们的原理,先回顾一下java中的以下知识点

【系统全面详细的学习jvm知识,推荐去尚硅谷看康师傅的视频课程】:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机)

基本类型和引用类型

  • 基本类型 也称为值类型包括
  1. 整数类型:byte、short、int、long
  2. 浮点类型:float、double
  3. 字符串类型:char
  4. 布尔类型:boolean
  • 引用类型
  1. Class(类)
  2. Interface(接口)
  3. Array(数组)

jvm内存模型

  • 内存模型图:

在这里插入图片描述

1. 虚拟机栈
  1. 主管Java程序的运行
  2. 每个线程都有自己的栈,栈中的数据都是以栈帧(Stack Frame)的格式存在
  3. 栈的操作遵循“先进后出”/“后进先出”原则,就是对栈帧的压栈和出栈
  4. 栈帧内部结构包括
    1. 局部变量表(Local Variables)
    2. 操作数栈(operand Stack)(或表达式栈)
    3. 动态链接(DynamicLinking)(或指向运行时常量池的方法引用)
    4. 方法返回地址(Return Address)(或方法正常退出或者异常退出的定义)
    5. 一些附加信息
  • 结构图

在这里插入图片描述

2. 堆
  1. 主要用于存储实例化的对象,数组。
  2. 堆也是Java内存管理的核心区域,是GC执行垃圾回收的重点区域。
  3. 堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据
  4. 堆的内部结构包括:
    1. Java 7及之前:新生区(年轻代/新生代)+养老区(老年代)+永久区
    2. Java 8及之后:新生区(年轻代/新生代)+养老区(老年代)+元空间
  5. 通过选项"-Xmx"和"-Xms"来进行设置堆的大小
  • 结构图

在这里插入图片描述
在这里插入图片描述

3. 方法区
  1. 方法区是一块独立于Java堆的内存空间。
  2. 是各个线程共享的内存区域,在JVM启动的时候被创建
  3. 方法区是主要存储
    1. 类信息:类型的完整有效名称(全名=包名.类名),直接父类的完整有效名,类型的修饰符
    2. 方法信息:方法名称,返回类型(或void),修饰符(public,private,protected…)
    3. 静态变量:static修饰的变量
    4. 运行时常量池:方法区的一部分,将编译期的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()));
}

  • 测试结果
    在这里插入图片描述

内存分配图

在这里插入图片描述

  • 分配解释:
  1. 我们通过SupportTeam supportTeam = new SupportTeam(); 创建了一个对象,在栈中分配一个名为supportTeam,只保存该对象在堆中的内存地址为1582071873。
  2. 在堆中分配SupportTeam类的对象实例,包含了基本数据类型为int的teamNo属性值为100,teamName属性值为“数据采集团队”,由于teamName类型为String类型,
    String类型是虽然引用类型,但其字面量的值“数据采集团队” 会被放入常量池,内存地址为:433287555。
  3. 栈中的supportTeam属性指向堆内存中的SupportTeam对象,其内存地址为1582071873。
  4. 通过ServiceReport serviceReport = new ServiceReport(); 在栈中分配一个名为serviceReport的对象,只保存该对象在堆中的内存地址为2084663827。
  5. 在堆中分配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()));
}



  • 测试结果

在这里插入图片描述

内存分配图

在这里插入图片描述

  • 分配解释:
  1. 我们通过 ServiceReport serviceReport2 = serviceReport; 将serviceReport对象复制一份给serviceReport2后,
    serviceReport2对象在栈中分配一个名为serviceReport2的对象,而内存地址仍然指向堆内存中的ServiceReport对象,其内存地址为2084663827。
  2. 此时堆内存中只有一份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()));
}


  • 测试结果

在这里插入图片描述

内存分配图

在这里插入图片描述

  • 分配解释:
  1. 我们通过 ServiceReport shallowCloneServiceReport = serviceReport.clone(); 将serviceReport对象浅拷贝一份
    得到shallowCloneServiceReport,此时栈中会分配一个名为shallowCloneServiceReport的对象,保存堆内存中的地址为1582071873,
    注意此时和原对象serviceReport内存地址值:2084663827是不同的,这也区别于复制的对象,因为
    复制时栈中的serviceReport 和 serviceReport2 对象在栈中的地址是相同的。

  2. 此时堆内存中拷贝了一份ServiceReport对象,注意通过浅拷贝后,堆内存中存在了一份新的ServiceReport对象,其地址为1582071873,这个堆对象是独立的新对象

    1. 引用类型成员变量reportName的内存地址为433287555,这和原对象serviceReport的reportName内存地址值:433287555是相同的,
      都仍然指向常量池中的字符串"数据采集服务",
    2. 引用类型的成员变量supportTeam对象地址为1908981452,这和原对象serviceReport的supportTeam对象地址值:1908981452是相同的,
      都仍然指向堆内存中同一个SupportTeam对象。
    3. 基本类型的成员变量id值为202401
  3. 接下来我们尝试修改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()));
    }

  • 测试结果

在这里插入图片描述

内存分配图

为了对比浅拷贝修改前的内存分配图,我们将他们放在一起比较

在这里插入图片描述

  • 分配解释:
  1. 通过观察测试结果serviceReport和shallowCloneServiceReport对象json数据,会发现出现了一些很有意思的事儿
  2. 我们通过浅拷贝对象shallowCloneServiceReport.setId(202402);//修改报告编号,发现原对象serviceReport的id值没有变化仍然为202401,
    这表明基本数据类型int的属性被完全拷贝了一份,基本数据类型属性是完全独立的,不会影响原对象。
  3. 通过浅拷贝对象shallowCloneServiceReport.setReportName(“数据分析服务”);//修改报告名称,发现原对象serviceReport的reportName值没有变化仍然为"数据采集服务",
    这里就诡异了,不是说String类型是引用类型,引用类型拷贝的是地址嘛,那为什么修改了shallowCloneServiceReport的reportName值内存地址变为27319466,
    原对象serviceReport的reportName地址还是433287555值没有变化呢?
    因为 String的值被放入了常量池中,当我们重新设置reportName属性时,JVM会先检查常量池中是否有相同的值,如果有相同的值,就会直接使用常量池中的值,返回常量池地址,
    如果常量池中没有则会将新的值放入常量池中,并返回常量池地址。所以“数据采集服务”常量池地址为433287555,“数据分析服务”常量池地址为27319466。
  4. 通过浅拷贝对象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()));
    }


  • 测试结果

在这里插入图片描述

内存分配图

在这里插入图片描述

  • 分配解释:
  1. 通过深拷贝获得deepCloneServiceReport对象,为当前对象重新赋值后, 通过测试结果中对象的json输出可以发现,两个对象的属性值各自独立不相同
  2. 通过原对象进行深拷贝获得deepCloneServiceReport对象, 首先在栈中创建一个名为deepCloneServiceReport的对象,保存指向堆内存的地址为:1582071873,
    在堆中完全创建一份ServiceReport对象,包括其属性引用类型的String变量reportName地址为1003752023指向常量池中“数据分析服务”,引用类型的对象supportTeam
    地址为:43287555指向堆中完全创建一份的SupportTeam对象
  3. 在栈中通过supportTeam对象创建一个新的地址为433287555的指向对内存的对象,在堆内存中完全创建一份SupportTeam的对象,包括其属性
    为引用类型的String变量teamName,地址为226744878
  4. 通过测试结果和内存分配图可以清晰看到,在深拷贝模式下,创建出的对象包括对象的属性无论是基本类型还是引用类型,其内存地址和属性值都完全不同于原对象,
    都完全会独立一份存在互不影响。

原型模式和传统对象赋值的性能测试对比

文章开头说到原型模式的优点时提到,“性能优化:减少创建新对象的开销,节省时间和资源”,接下来通过程序示例来观察实际如何,为了更加明显的看到耗时情况,我们对实体类进行
简单的修改

  1. 将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);
        }
    }

}

  1. 为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万次报告对象,测试结果发现耗时 传统复制>深拷贝>浅拷贝
  1. 传统对象复制测试耗时

在这里插入图片描述

  1. 浅拷贝测试耗时

在这里插入图片描述

  1. 深拷贝测试耗时

在这里插入图片描述


  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值