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);
}
- 优点是比较好理解,简单易操作;
- 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低;
- 总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活。
原型模式
- 原型模式(
Prototype
模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象; - 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节;
- 工作原理是: 通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即
对象.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不可变,传入新的,当然指向新的地址了。
二、深拷贝
-
复制对象的所有基本数据类型的成员变量值
-
为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝
-
深拷贝实现方式有两种
- 重写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);
}
总结
原型模式的注意事项和细节:
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率;
- 不用重新初始化对象,而是动态地获得对象运行时的状态;
- 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码;
- 需要注意浅拷贝的成员变量数据类型是引用数据类型(对象)的时候;
- 在实现深克隆的时候可能需要比较复杂的代码建议使用序列化方式;
相关文章:
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>搜索软件包列表,找到与搜索关键字匹配的包 dpkg与grep结合查找特定的包 dpkg -s <package>:查看某个安装包的详细信息 安装包 apt安装命令 更新…...
[Ubuntu] linux之Ubuntu18.04的下载及在虚拟机中详细安装过程(附有下载链接)
前言 ubuntu 链接:https://pan.quark.cn/s/283509d0d36e 提取码:dfT1 链接失效(可能被官方和谐)可评论或私信我重发 下载压缩包后解压 !!安装路径不要有中文 下载后解压得到.iso文件,不要放在…...
ffmpeg安装(windows)
ffmpeg安装-windows 前言ffmpeg安装路径安装说明 前言 ffmpeg的安装也是开箱即用的,并没有小码哥说的那么难 ffmpeg安装路径 这就下载好了! 安装说明 将上面的bin目录加入到环境变量,然后在cmd中测试一下: C:\Users\12114\Desktop\test\TaskmgrPlayer\x64\Debug>ffmpe…...
服务器数据恢复—raid6阵列硬盘被误重组为raid5阵列的数据恢复案例
服务器存储数据恢复环境: 存储中有一组由12块硬盘组建的RAID6阵列,上层linux操作系统EXT3文件系统,该存储划分3个LUN。 服务器存储故障&分析: 存储中RAID6阵列不可用。为了抢救数据,运维人员使用原始RAID中的部分…...
linux内核编译启动总结
linux kernel 编译 升级汇总 写在前面内核编译获取kernel代码开始前的准备工作 编译过程1\.解压与净化将下载好的linux内核解压至/usr/src 2\. 得到源代码后,将其净化3\. 配置要进行编译的内核4.编译内核. (15分钟)5.编译模块.方法1:方法2: 6…...
Android Studio的AI工具插件使用介绍
Android Studio的AI工具插件使用介绍 一、前言 Android Studio 的 AI 工具插件具有诸多重要作用,以下是一些常见的方面: 代码生成与自动补全 代码优化与重构 代码解读 学习与知识获取 智能搜索与资源推荐实际使用中可以添加注释,解读某段代…...
本地部署 WireGuard 无需公网 IP 实现异地组网
WireGuard 是一个高性能、极简且易于配置的开源虚拟组网协议。使用路由侠内网穿透使其相互通讯。 第一步,服务端(假设为公司电脑)和客户端(假设为公司外的电脑)安装部署 WireGuard 1,点此下载(…...
asyncio.ensure_future 与 asyncio.create_task:Python异步编程中的选择
asyncio.ensure_future 与 asyncio.create_task:Python异步编程中的选择 引言asyncio.ensure_futureasyncio.create_task两者的区别参数接受范围任务调度的保证代码可读性 哪个更好?使用asyncio.create_task使用asyncio.ensure_future 结论参考 引言 在…...
CTF之密码学(密码特征分析)
一.MD5,sha1,HMAC,NTLM 1.MD5:MD5一般由32/16位的数字(0-9)和字母(a-f)组成的字符串 2.sha1:这种加密的密文特征跟MD5差不多,只不过位数是40(sha256:64位;sha512:128位) 3.HMAC:这…...
JVM调优篇之JVM基础入门AND字节码文件解读
目录 Java程序编译class文件内容常量池附录-访问标识表附录-常量池类型列表 Java程序编译 Java文件通过编译成class文件后,通过JVM虚拟机解释字节码文件转为操作系统执行的二进制码运行。 规范 Java虚拟机有自己的一套规范,遵循这套规范,任…...
EXCEL截取某一列从第一个字符开始到特定字符结束的字符串到新的一列
使用EXCEL中的公式进行特定截取 假设列A是一组产品的编码,我们需要的数据是“-”之前的字段。 我们需要在B1单元格输入公式“LEFT(A1,SEARCH("-",A1)-1)”然后选中B1至B4单元格,按“CTRLD”向下填充,就可以得出其它几行“-”之前的…...
数据库期末复习题库
1. Mysql日志功能有哪些? 记录日常操作和错误信息,以便了解Mysql数据库的运行情况,日常操作,错误信息和进行相关的优化。 2. 数据库有哪些备份方法 完全备份:全部都备份一遍表备份:只提取数据库中的数据࿰…...
私有库gitea安装
一 gitea是什么 Gitea是一款自助Git服务,简单来说,就是可以一个私有的github。 搭建很容易。 Gitea依赖于Git。 类似Gitea的还有GitHub、Gitee、GitLab等。 以下是安装步骤。 二 安装sqilite 参考: 在windows上安装sqlite 三 安装git…...
关于最近win11不能使用ie,而不能使用考试客户端的解决方法
弄ie的那个我感觉是非常难的,所以我的是另一种的方法 下载360浏览器(不是360全家桶)360安全浏览器-全面保护上网安全,4亿用户共同选择(上面的是官网,不要下载错了,还有安装界面注意不要勾选一下…...
深度学习之Mask-R-CNN
1.1 Mask-RCNN 的网络结构示意图 其中黑色部分为原来的Faster-RCNN,红色部分为在Faster网络上的修改: 1)将ROI Pooling层替换成了ROIAlign; 2)添加并列的FCN层(Mask层); …...
css包含块
包含块 出现 在css中一些属性的计算可能超出你的预料,在普遍情况下会认为定位属性和百分比的宽高是根据父元素计算的,但是准确来说他们都是根据元素所在的包含块来计算的,所以掌握包含块的知识是非常关键的。 内容 在CSS中,“…...
混沌工程/混沌测试/云原生测试/云平台测试
背景 私有云/公有云/混合云等具有复杂,分布式,环境多样性等特点,许多特殊场景引发的线上问题很难被有效发现。所以需要引入混沌工程,建立对系统抵御生产环境中失控条件的能力以及信心,提高系统面对未知风险得能力。 …...
研发设计数字化:PLM、PDM、ERP介绍及其区别
一、产品全生命周期管理的定义 1.1 产品全生命周期(PLM)发展背景 目前,数字化设计与制造的技术(如CAX、DFX等)已经在产品开发中得到广泛应用,而各种企业和产品管理软件(如ERP、SCM、PDM、CRM等…...
Python练习51
Python日常练习 题目: 调用函数fun判断一个三位数是否“水仙花数”。 在main函数中从键盘输入一个三位数,并输 出判断结果。请编写fun函数。 说明: 所谓“水仙花数”是指一3位数,其各位数字立方和 等于该数本…...
Qt 前置课程 QtNFC
文章目录 详解 Qt NFC 模块(QtNFC)1. 什么是 NFC?2. NFC 的原理2.1 主动设备与被动设备2.2 三种工作模式2.3 数据交换 3. QtNFC 模块概述4. 使用 QtNFC 模块4.1 配置 .pro 文件 5. NFC 的常见应用场景6. QtNFC 模块的主要类6.1 QNearFieldMan…...
公司网站建设工作总结/哈尔滨网络优化推广公司
MySQL 在修改表结构的时候可能会中断产品的正常运行影响用户体验,甚至更坏的结果,丢失数据。不是所有的数据库管理员、程序员、系统管理员都非常了解MySQL能避免这种情况。DBA会经常碰到这种生产中断的情况,当升级脚本修改了应用层和数据库层…...
做网站搞友情链接/哪个公司的网站制作
Oracle数据有个字段是存储url的,内容有特殊字符&,如果直接想执行查询或插入操作的话有问题,如下:在plsql里边执行:update userinfo set pageurltest.php?page1&pagesize10 where idtest这条sql语句往数据库的pageurl字段…...
php如何做网站/seo排名大概多少钱
捡拾的过程中累加捡拾硬币,要求最后捡拾的硬币累加和是最大的。捡拾的过程中,相邻的两个硬币不能同时被捡拾。比如:有以下几个硬币值:[5, 1, 2, 10, 5, 2], 可以捡拾的方案有,(5, 2, 5) 或 (5, 10, 2)解题思路…...
网络营销企业网站优化/长沙seo网站
用xcode+phonegap 开发ios手机应用的时候,xcode会缓存文件,每次更新文件,重新编译时新更新的功能,并未不能使用。这时应当在编译之前,先product->clean一下,让后再编译执行。新功…...
wordpress 汉化包/汨罗网站seo
问题1解决启动服务:service mysqld start;/sbin/iptables -I INPUT -p tcp --dport 8011 -j ACCEPT #开启8011端口/etc/rc.d/init.d/iptables save #保存配置/etc/rc.d/init.d/iptables restart #重启服务#查看端口是否已经开放/etc/init.d/iptables status问题2解决…...
icp备案查询站长工具/seo的作用
网络与通信 N e t w o r k&C o mmu n i c a t i o n计算机网络技术的发展模式研究文/李祥龙研究、探索、试验以及考证,也伴随着新兴技2计算机网络技术快速发展模式分析计算机技术的发展模式对一个国家、地区和社会的成长有着十分重要的影响,其制度、…...