当前位置: 首页 > news >正文

Java设计模式 —— 【创建型模式】原型模式(浅拷贝、深拷贝)详解

文章目录

  • 前言
  • 原型模式
  • 一、浅拷贝
      • 1、案例
      • 2、引用数据类型
  • 二、深拷贝
      • 1、重写clone()方法
      • 2、序列化
  • 总结


前言

先看一下传统的对象克隆方式:

原型类:

public class Student {private String name;public Student(String name) {this.name = name;}public String getName() {return name;}@Overridepublic String toString() {return "Student{'name' = " + name + "}, " + "hashCode = " + this.hashCode();}
}

克隆:

@Test
public void test(){//原型对象Student student = new Student("张三");//克隆对象Student student1 = new Student(student.getName());Student student2 = new Student(student.getName());Student student3 = new Student(student.getName());System.out.println("原型对象: " + student);System.out.println("克隆对象1: " + student1);System.out.println("克隆对象2: " + student2);System.out.println("克隆对象3: " + student3);
}

在这里插入图片描述

  1. 优点是比较好理解,简单易操作;
  2. 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低;
  3. 总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活。

原型模式

  1. 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象;
  2. 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节;
  3. 工作原理是: 通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

原型模式包含如下角色:

  • 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
  • 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

在这里插入图片描述
原型模式的克隆分为浅克隆和深克隆。

  • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
  • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

一、浅拷贝

1、案例

对于上文中的克隆方法加以改进:

原型类:

public class Student implements Cloneable {private String name;public Student(String name) {System.out.println("原型对象创建成功!!!");this.name = name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Student{'name' = " + name + "}, " + "hashCode = " + this.hashCode();}//实现对象克隆@Overrideprotected Object clone() throws CloneNotSupportedException {System.out.println("克隆成功!!!");return super.clone();}
}

测试:

@Test
public void test1() throws CloneNotSupportedException {Student newStudent = new Student("张三");Student cloneStudent = (Student) newStudent.clone();System.out.println("原型对象: " + newStudent);System.out.println("克隆对象: " + cloneStudent);
}

在这里插入图片描述

2、引用数据类型

  • 上述案例中我们可以看出克隆是克隆成功了,并且没有走构造方法,所克隆出的对象地址和原对象地址不一样,是新的对象;

  • 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象;

  • 但是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,并没有new 一个新的对象,而是进行引用传递指向原有的引用;

  • 在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

我们添加原型类的成员变量:

School:

public class School {private String name;public School(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

Student:

public class Student implements Cloneable {private String name;private School school;public Student(String name, School school) {this.name = name;this.school = school;}public void setName(String name) {this.name = name;}public School getSchool() {return school;}@Overridepublic String toString() {return "Student{'name' = " + name + ", 'school' = " + school.getName() + "}, " +"Student.hashCode = " + this.hashCode() + ", " +"name.hashCode" + name.hashCode() + ", " +"School.hashCode = " + school.hashCode();}//实现对象克隆@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}

测试:

@Test
public void test2() throws CloneNotSupportedException {Student newStudent = new Student("张三", new School("清华"));Student cloneStudent = (Student) newStudent.clone();System.out.println("原型对象:" + newStudent);System.out.println("克隆对象:" + cloneStudent);System.out.println("=====================修改克隆对象信息========================");cloneStudent.setName("李四");cloneStudent.getSchool().setName("北大");System.out.println("修改后的原型对象:" + newStudent);System.out.println("修改后的克隆对象:" + cloneStudent);
}

在这里插入图片描述
上述案例可以看出:

  • 克隆确实产生新的对象,但是引用数据类型只是进行了引用传递;
  • 以至于我们修改了cloneStudent的学校,newStudent也随之修改了;
  • 那为什么String也是引用数据类型,cloneStudent的那么由“张三”改为“李四”,而newStudent没有呢,那是因为String不可变,传入新的,当然指向新的地址了。

二、深拷贝

  1. 复制对象的所有基本数据类型的成员变量值

  2. 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝

  3. 深拷贝实现方式有两种
    - 重写clone方法来实现深拷贝

    - 通过对象序列化实现深拷贝(推荐)

1、重写clone()方法

  • 重写clone方法主要是在原有的克隆的基础上,将引用数据类型再进行嵌套克隆;

  • 每个被引用的类也要实现Cloneable接口,重写clone()方法;

  • 这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背开闭原则。

School:

public class School implements Cloneable{private String name;public School(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}

Student:

public class Student implements Cloneable {private String name;private School school;public Student(String name, School school) {this.name = name;this.school = school;}public void setName(String name) {this.name = name;}public School getSchool() {return school;}@Overridepublic String toString() {return "Student{'name' = " + name + ", 'school' = " + school.getName() + "}, " +"Student.hashCode = " + this.hashCode() + ", " +"name.hashCode" + name.hashCode() + ", " +"School.hashCode = " + school.hashCode();}//实现对象克隆@Overrideprotected Object clone() throws CloneNotSupportedException {//克隆基本数据类型以及StringStudent student = (Student) super.clone();//引用数据类型再进行克隆student.school = (School) student.getSchool().clone();return student;}
}

测试:

@Test
public void test3() throws CloneNotSupportedException {Student newStudent = new Student("张三", new School("清华"));Student cloneStudent = (Student) newStudent.clone();System.out.println("原型对象:" + newStudent);System.out.println("克隆对象:" + cloneStudent);System.out.println("=====================修改克隆对象信息========================");cloneStudent.setName("李四");cloneStudent.getSchool().setName("北大");System.out.println("修改后的原型对象:" + newStudent);System.out.println("修改后的克隆对象:" + cloneStudent);
}

在这里插入图片描述

2、序列化

涉及到的所有类必须实现Serializable接口,否则会抛NotSerializableException异常。

School:

public class School implements Serializable{private String name;public School(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

Student:

public class Student implements Serializable {private String name;private School school;public Student(String name, School school) {this.name = name;this.school = school;}public void setName(String name) {this.name = name;}public School getSchool() {return school;}@Overridepublic String toString() {return "Student{'name' = " + name + ", 'school' = " + school.getName() + "}, " +"Student.hashCode = " + this.hashCode() + ", " +"name.hashCode" + name.hashCode() + ", " +"School.hashCode = " + school.hashCode();}public Student deepClone() {ByteArrayOutputStream bos = null;ObjectOutputStream oos = null;ByteArrayInputStream bis = null;ObjectInputStream ois = null;try {//序列化bos = new ByteArrayOutputStream();oos = new ObjectOutputStream(bos);oos.writeObject(this);//反序列化bis = new ByteArrayInputStream(bos.toByteArray());ois = new ObjectInputStream(bis);return (Student) ois.readObject();} catch (Exception e) {e.printStackTrace();return null;} finally {try {if (bos != null) bos.close();if (oos != null) oos.close();if (bis != null) bis.close();if (ois != null) ois.close();} catch (IOException e) {e.printStackTrace();}}}
}

测试:

@Test
public void test4() throws CloneNotSupportedException {Student newStudent = new Student("张三", new School("清华"));Student cloneStudent = newStudent.deepClone();System.out.println("原型对象:" + newStudent);System.out.println("克隆对象:" + cloneStudent);System.out.println("=====================修改克隆对象信息========================");cloneStudent.setName("李四");cloneStudent.getSchool().setName("北大");System.out.println("修改后的原型对象:" + newStudent);System.out.println("修改后的克隆对象:" + cloneStudent);
}

在这里插入图片描述


总结

原型模式的注意事项和细节:

  1. 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率;
  2. 不用重新初始化对象,而是动态地获得对象运行时的状态;
  3. 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码;
  4. 需要注意浅拷贝的成员变量数据类型是引用数据类型(对象)的时候;
  5. 在实现深克隆的时候可能需要比较复杂的代码建议使用序列化方式;

相关文章:

Java设计模式 —— 【创建型模式】原型模式(浅拷贝、深拷贝)详解

文章目录 前言原型模式一、浅拷贝1、案例2、引用数据类型 二、深拷贝1、重写clone()方法2、序列化 总结 前言 先看一下传统的对象克隆方式: 原型类: public class Student {private String name;public Student(String name) {this.name name;}publi…...

SciAssess——评估大语言模型在科学文献处理中关于模型的记忆、理解和分析能力的基准

概述 大规模语言模型(如 Llama、Gemini 和 GPT-4)的最新进展因其卓越的自然语言理解和生成能力而备受关注。对这些模型进行评估对于确定其局限性和潜力以及促进进一步的技术进步非常重要。为此,人们提出了一些特定的基准来评估大规模语言模型…...

SQLModel与FastAPI结合:构建用户增删改查接口

SQLModel简介 SQLModel是一个现代化的Python库,旨在简化与数据库的交互。它结合了Pydantic和SQLAlchemy的优势,使得定义数据模型、进行数据验证和与数据库交互变得更加直观和高效。SQLModel由FastAPI的创始人Sebastin Ramrez开发,专为与FastA…...

【RISC-V CPU debug 专栏 2.3 -- Run Control】

文章目录 Run ControlHart 运行控制状态位状态信号操作流程时间与实现注意事项Run Control 在 RISC-V 调试架构中,运行控制模块通过管理多个状态位来对硬件线程(harts)的执行进行调节和控制。这些状态位帮助调试器请求暂停或恢复 harts,并在 hart 复位时进行控制。以下是运…...

探索 IntelliJ IDEA 中 Spring Boot 运行配置

前言 IntelliJ IDEA 作为一款功能强大的集成开发环境(IDE),为 Spring Boot 应用提供了丰富的运行配置选项,定义了如何在 IntelliJ IDEA 中运行 Spring Boot 应用程序,当从主类文件运行应用程序时,IDE 将创建…...

三除数枚举

给你一个整数 n 。如果 n 恰好有三个正除数 ,返回 true ;否则,返回 false 。 如果存在整数 k ,满足 n k * m ,那么整数 m 就是 n 的一个 除数 。 输入:n 4 输出:true 解释:4 有三…...

【051】基于51单片机温度计【Proteus仿真+Keil程序+报告+原理图】

☆、设计硬件组成:51单片机最小系统DS18B20温度传感器LCD1602液晶显示按键设置蜂鸣器LED灯。 1、本设计采用STC89C51/52、AT89C51/52、AT89S51/52作为主控芯片; 2、采用DS18B20温度传感器测量温度,并且通过LCD1602实时显示温度;…...

[Java]微服务之服务保护

雪崩问题 微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩 雪崩问题产生的原因是什么? 微服务相互调用,服务提供者出现故障或阻塞。服务调用者没有做好异常处理,导致自身故障。调用链中的所有服…...

自动驾驶目标检测融合全貌

1、early fusion 早期融合,特点用到几何空间转换3d到2d或者2d到3d的转换,用像素找点云或者用点云找像素。 2、deep fusion 深度融合,也是特征级别融合,也叫多模态融合,如bevfusion范式 3、late fusion 晚融合&#x…...

消息框(Message Box)的测试方法和测试用例

我来帮你了解消息框(Message Box)的测试方法和测试用例的编写。 我已经创建了一个测试用例示例,让我为你解释消息框测试的主要方面: 测试维度: 功能性测试:验证消息框的基本功能是否正常样式测试:确认不同类型消息框…...

Ubuntu 包管理

APT&dpkg 查看已安装包 查看所有已经安装的包 dpkg -l 查找包 apt search <package_name>搜索软件包列表&#xff0c;找到与搜索关键字匹配的包 dpkg与grep结合查找特定的包 dpkg -s <package>&#xff1a;查看某个安装包的详细信息 安装包 apt安装命令 更新…...

[Ubuntu] linux之Ubuntu18.04的下载及在虚拟机中详细安装过程(附有下载链接)

前言 ubuntu 链接&#xff1a;https://pan.quark.cn/s/283509d0d36e 提取码&#xff1a;dfT1 链接失效&#xff08;可能被官方和谐&#xff09;可评论或私信我重发 下载压缩包后解压 &#xff01;&#xff01;安装路径不要有中文 下载后解压得到.iso文件&#xff0c;不要放在…...

ffmpeg安装(windows)

ffmpeg安装-windows 前言ffmpeg安装路径安装说明 前言 ffmpeg的安装也是开箱即用的,并没有小码哥说的那么难 ffmpeg安装路径 这就下载好了! 安装说明 将上面的bin目录加入到环境变量,然后在cmd中测试一下: C:\Users\12114\Desktop\test\TaskmgrPlayer\x64\Debug>ffmpe…...

服务器数据恢复—raid6阵列硬盘被误重组为raid5阵列的数据恢复案例

服务器存储数据恢复环境&#xff1a; 存储中有一组由12块硬盘组建的RAID6阵列&#xff0c;上层linux操作系统EXT3文件系统&#xff0c;该存储划分3个LUN。 服务器存储故障&分析&#xff1a; 存储中RAID6阵列不可用。为了抢救数据&#xff0c;运维人员使用原始RAID中的部分…...

linux内核编译启动总结

linux kernel 编译 升级汇总 写在前面内核编译获取kernel代码开始前的准备工作 编译过程1\.解压与净化将下载好的linux内核解压至/usr/src 2\. 得到源代码后,将其净化3\. 配置要进行编译的内核4.编译内核. &#xff08;15分钟&#xff09;5.编译模块.方法1:方法2&#xff1a; 6…...

Android Studio的AI工具插件使用介绍

Android Studio的AI工具插件使用介绍 一、前言 Android Studio 的 AI 工具插件具有诸多重要作用&#xff0c;以下是一些常见的方面&#xff1a; 代码生成与自动补全 代码优化与重构 代码解读 学习与知识获取 智能搜索与资源推荐实际使用中可以添加注释&#xff0c;解读某段代…...

本地部署 WireGuard 无需公网 IP 实现异地组网

WireGuard 是一个高性能、极简且易于配置的开源虚拟组网协议。使用路由侠内网穿透使其相互通讯。 第一步&#xff0c;服务端&#xff08;假设为公司电脑&#xff09;和客户端&#xff08;假设为公司外的电脑&#xff09;安装部署 WireGuard 1&#xff0c;点此下载&#xff08;…...

asyncio.ensure_future 与 asyncio.create_task:Python异步编程中的选择

asyncio.ensure_future 与 asyncio.create_task&#xff1a;Python异步编程中的选择 引言asyncio.ensure_futureasyncio.create_task两者的区别参数接受范围任务调度的保证代码可读性 哪个更好&#xff1f;使用asyncio.create_task使用asyncio.ensure_future 结论参考 引言 在…...

CTF之密码学(密码特征分析)

一.MD5,sha1,HMAC,NTLM 1.MD5&#xff1a;MD5一般由32/16位的数字(0-9)和字母(a-f)组成的字符串 2.sha1&#xff1a;这种加密的密文特征跟MD5差不多&#xff0c;只不过位数是40&#xff08;sha256&#xff1a;64位&#xff1b;sha512:128位&#xff09; 3.HMAC&#xff1a;这…...

JVM调优篇之JVM基础入门AND字节码文件解读

目录 Java程序编译class文件内容常量池附录-访问标识表附录-常量池类型列表 Java程序编译 Java文件通过编译成class文件后&#xff0c;通过JVM虚拟机解释字节码文件转为操作系统执行的二进制码运行。 规范 Java虚拟机有自己的一套规范&#xff0c;遵循这套规范&#xff0c;任…...

Llama-3.2V-11B-cot高效部署:双卡4090下11B模型加载时间缩短至92s

Llama-3.2V-11B-cot高效部署&#xff1a;双卡4090下11B模型加载时间缩短至92s 1. 项目概述 Llama-3.2V-11B-cot是基于Meta Llama-3.2V-11B-cot多模态大模型开发的高性能视觉推理工具。该工具针对双卡RTX 4090环境进行了深度优化&#xff0c;通过一系列技术创新将11B大模型的加…...

Python AI 用例工具部署踩坑实录:Docker镜像体积暴增300%、GPU显存泄漏、模型热加载失败的5个根因与秒级修复方案

第一章&#xff1a;Python AI 用例工具部署的典型失败图谱在真实生产环境中&#xff0c;Python AI 工具链&#xff08;如 LangChain、LlamaIndex、FastAPI 封装的推理服务&#xff09;的部署失败往往并非源于模型能力缺陷&#xff0c;而是由基础设施、依赖冲突与配置漂移引发的…...

Kubernetes 集群管理新选择:Kuboard 图形化界面实战解析

1. 为什么你需要Kuboard这样的Kubernetes图形化管理工具 如果你刚开始接触Kubernetes&#xff0c;可能会被它复杂的命令行操作吓到。记得我第一次使用kubectl时&#xff0c;光是记住各种命令参数就花了两周时间。后来团队规模扩大&#xff0c;管理多个集群时&#xff0c;命令行…...

MGeo门址解析应用场景:房产中介平台房源地址自动标准化与GIS热力图生成

MGeo门址解析应用场景&#xff1a;房产中介平台房源地址自动标准化与GIS热力图生成 1. 引言&#xff1a;房产中介的地址之痛 想象一下&#xff0c;你是一家房产中介公司的运营人员。每天&#xff0c;你的同事和合作方会通过各种渠道收集到成百上千条房源信息&#xff1a;有的…...

电脑c盘变红了怎么清理?C盘清理工具与方法

电脑c盘变红了怎么清理&#xff1f;问题不难解决&#xff0c;关键是选对方法工具&#xff01;下面介绍实用的清理C盘方法&#xff0c;便于你解决C盘变红的问题哦&#xff01; 关于C盘清理工具&#xff0c;给大家安排一款针对C盘爆满的清理神器---Windows - Cleaner&#xff0c…...

STC-50kg

【广州兰瑟★电子-杨工】提供的STC-50kg 是美国威世世铨&#xff08;Vishay Celtron&#xff09;旗下一款经典的 S 型拉压双向称重 / 测力传感器&#xff0c;量程 50 公斤 (50kgf / 490N)。 一、核心参数&#xff08;标准型&#xff09; 量程&#xff1a;50 kg (拉力 / 压力双向…...

Unsloth Docker部署详解:从零开始搭建训练环境

Unsloth Docker部署详解&#xff1a;从零开始搭建训练环境 1. 环境准备与Docker安装 1.1 系统要求检查 在开始之前&#xff0c;请确保你的系统满足以下基本要求&#xff1a; 64位Linux系统&#xff08;推荐Ubuntu 22.04&#xff09;NVIDIA显卡驱动已安装&#xff08;建议版…...

AI显微镜-Swin2SR基础教程:理解‘细节重构技术’对AI生成图的价值

AI显微镜-Swin2SR基础教程&#xff1a;理解‘细节重构技术’对AI生成图的价值 1. 从模糊到高清&#xff1a;AI超分的革命性突破 你是否曾经遇到过这样的情况&#xff1a;AI生成了一张很有创意的图片&#xff0c;但分辨率太低&#xff0c;放大后全是马赛克&#xff1b;或者找到…...

【实战篇】OneWire时序精解:从复位脉冲到DS18B20温度读取

1. OneWire协议基础&#xff1a;单线通信的魔法 第一次接触OneWire&#xff08;单总线&#xff09;协议时&#xff0c;我盯着那根孤零零的信号线愣了半天——这根线既要供电又要传数据&#xff0c;还要协调多个设备&#xff0c;简直像用一根吸管同时喝奶茶、吃珍珠和传情书。但…...

LeetCode 153. 旋转排序数组找最小值:二分最优思路

LeetCode中等难度的经典题目——153. 寻找旋转排序数组中的最小值。这道题的核心考点是「二分查找」&#xff0c;难点在于如何利用“旋转排序数组”的特性&#xff0c;在O(log n)时间复杂度内找到最小值&#xff0c;也是面试中常考的二分变形题。 一、题目解读&#xff1a;读懂…...