Java 中的浅拷贝和深拷贝
无论是浅拷贝还是深拷贝,都可以通过 Object 类的 clone() 方法来完成:
/*** 拷贝** @author qiaohaojie* @date 2023/3/5 15:58*/
public class CloneTest {public static void main(String[] args) throws Exception {Person person1 = new Person(23, "青花椒");Person person2 = (Person) person1.clone();System.out.println("浅拷贝后:");System.out.println("person1:" + person1); // person1:Person{age=23, name='青花椒', student=null}System.out.println("person2:" + person2); // person2:Person{age=23, name='青花椒', student=null}}
}
扒一下 clone()
方法的源码:
// Java 1.8
protected native Object clone() throws CloneNotSupportedException;// Java 9
@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
其中,@HotSpotIntrinsicCandidate
注解是 Java 9 引入的一个注解,被它标记的方法,在 HotSpot 虚拟机中会有一套高效的实现。需要注意的是,clone() 方法同时是一个本地(native)方法,它的具体实现会交给 HotSpot 虚拟机,那就意味着虚拟机在运行该方法的时候,会将其替换为更高效的 C/C++ 代码,进而调用操作系统去完成对象的克隆工作。
1. 浅拷贝
1.1 基本数据类型
Person 类有两个字段,分别是 age 和 name,然后重写 toString() 方法:
/*** person类** @author qiaohaojie* @date 2023/3/5 16:09*/
@Data
public class Person implements Cloneable {private int age;private String name;public Person(int age, String name) {this.age = age;this.name = name;}@Overridepublic String toString() {return super.toString().substring(18) + "{" +"age=" + age +", name='" + name + '\'' +'}';}
}
Cloneable 接口是一个标记接口,接口里没有内容:
public interface Cloneable {
}
它只是一个标记接口,为什么要实现它呢?
标记接口的作用其实就是用来表示某个功能在执行的时候是合法的。
如果一个类没有实现 Cloneable 接口,即使它重写了 clone() 方法了,但它依然是无法调用该方法进行对象克隆的,程序在执行 clone() 方法的时候会抛出 CloneNotSupportedException 异常:
Exception in thread "main" java.lang.CloneNotSupportedException
测试类:
/*** 浅拷贝测试-基本类型字段** @author qiaohaojie* @date 2023/3/5 15:58*/
public class CloneTest {public static void main(String[] args) throws Exception {Person person1 = new Person(23, "青花椒");Person person2 = (Person) person1.clone();System.out.println("浅拷贝后:");System.out.println("person1:" + person1); // person1:Person@86accb4e{age=23, name='青花椒'}System.out.println("person2:" + person2); // person2:Person@6c58a0a6{age=23, name='青花椒'}person2.setName("程序猿");System.out.println("修改后:");System.out.println("person1:" + person1); // person1:Person@86accb4e{age=23, name='青花椒'}System.out.println("person2:" + person2); // person2:Person@6c58a0a6{age=23, name='程序猿'}}
}
步骤大概是这样的:
- 通过 new 关键字声明了一个 Person 对象,并将其赋值给 person1;
- 调用 clone() 方法进行对象的拷贝,并将其赋值给 person2;
- 打印 person1 和 person2;
- 将 person2 的 name 字段修改为 “程序猿”;
- 打印 person1 和 person2。
从结果可以看出,浅拷贝后 person1 和 person2 引用了不同的对象,但是值是相同的,说明拷贝成功了。之后再修改 person2 的 name 字段时,引用是这样的:
1.2 引用数据类型
再自定义一个 Student 类,有 score 和 name 两个字段:
/*** 学生类** @author qiaohaojie* @date 2023/3/5 16:31*/
@Data
public class Student implements Cloneable {private double score;private String name;public Student(double score, String name) {this.score = score;this.name = name;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}@Overridepublic String toString() {return super.toString().substring(18) + "{" +"score=" + score +", name='" + name + '\'' +'}';}
}
修改 Person 类,把 Student 类对象当做一个成员变量:
/*** person类** @author qiaohaojie* @date 2023/3/5 16:09*/
@Data
public class Person implements Cloneable {private int age;private String name;private Student student;public Person(int age, String name) {this.age = age;this.name = name;}// 浅拷贝@Overridepublic Object clone() throws CloneNotSupportedException {return super.clone();}@Overridepublic String toString() {return super.toString().substring(18) + "{" +"age=" + age +", name='" + name + '\'' +", student=" + student +'}';}
}
测试类:
/*** 浅拷贝测试-引用类型字段** @author qiaohaojie* @date 2023/3/5 16:46*/
public class CloneTest2 {public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person(23, "青花椒");Student student1 = new Student(93.2, "张三");person1.setStudent(student1);Person person2 = (Person) person1.clone();System.out.println("浅拷贝后:");System.out.println("person1:" + person1); // person1:Person@ee8f2be0{age=23, name='青花椒', student=Student@67e260bd{score=93.2, name='张三'}}System.out.println("person2:" + person2); // person2:Person@9853e6e6{age=23, name='青花椒', student=Student@67e260bd{score=93.2, name='张三'}}Student student2 = person2.getStudent();student1.setName("李四");student1.setScore(88.8);System.out.println("person2.变更后:");System.out.println("person1:" + person1); // person1:Person@ee8f2be0{age=23, name='青花椒', student=Student@67e260bd{score=88.8, name='李四'}}System.out.println("person2:" + person2); // person2:Person@1f00b209{age=23, name='青花椒', student=Student@67e260bd{score=88.8, name='李四'}}}
}
步骤大概是这样的:
- 通过 new 关键字声明了一个 Person 对象,并将其赋值给 person1;
- 通过 new 关键字声明了一个 Stident 对象,并将其赋值给 student1;
- 把 person1 的 student 字段设置为 student1;
- 调用 clone() 方法进行对象的拷贝,并将其赋值给 person2;
- 打印 person1 和 person2;
- 获取 person2 的 student 字段,并将其赋值给 student2;
- 把 student2 的 name 字段修改为 “李四”,score 字段修改为 88.8;
- 打印 person1 和 person2。
与基本数据类型拷贝不同的是,person2.student 修改后,person1.student 也发生了改变。这是因为字符串 String 是不可变对象,一个新的值必须在字符串常量池中开辟一段新的内存空间,而自定义对象的内存地址并没有发生改变,只是对应的字段值发生了改变:
值得注意的是,浅拷贝克隆的对象中,引用类型的字段指向的是同一个对象,当改变任何一个对象,另外一个对象也会随之改变,除去字符串的特殊性外。
2. 深拷贝
深拷贝和浅拷贝不同的是,深拷贝中的引用类型字段也会克隆一份,当改变任何一个对象时,另外一个对象不会随之改变。
Student 类,重写了 clone() 方法,并实现了 Cloneable 接口,为的就是深拷贝时也能够克隆该字段:
/*** 学生类** @author qiaohaojie* @date 2023/3/5 16:31*/
@Data
public class Student implements Cloneable {private double score;private String name;public Student(double score, String name) {this.score = score;this.name = name;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}@Overridepublic String toString() {return super.toString().substring(18) + "{" +"score=" + score +", name='" + name + '\'' +'}';}
}
Person 类,与之前不同的是,clone() 方法中,不再只调用 Object 的 clone() 方法对 Person 进行克隆了,还对 Student 也进行了克隆:
/*** person类** @author qiaohaojie* @date 2023/3/5 16:09*/
@Data
public class Person implements Cloneable {private int age;private String name;private Student student;public Person(int age, String name) {this.age = age;this.name = name;}// 深拷贝@Overridepublic Object clone() throws CloneNotSupportedException {Person person = (Person) super.clone();person.setStudent(student);person.setStudent((Student) person.getStudent().clone());return super.clone();}@Overridepublic String toString() {return super.toString().substring(18) + "{" +"age=" + age +", name='" + name + '\'' +", student=" + student +'}';}
}
测试类:
/*** 深拷贝测试** @author qiaohaojie* @date 2023/3/5 17:06*/
public class CloneTest3 {public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person(23, "青花椒");Student student1 = new Student(93.2, "张三");person1.setStudent(student1);Person person2 = (Person) person1.clone();System.out.println("深拷贝后:");System.out.println("person1:" + person1); // person1:Person@ee8f2be0{age=23, name='青花椒', student=Student@67e260bd{score=93.2, name='张三'}}System.out.println("person2:" + person2); // person2:Person@1f00b209{age=23, name='青花椒', student=Student@9853e6e6{score=93.2, name='张三'}}Student student2 = person2.getStudent();student2.setName("李四");student2.setScore(88.8);System.out.println("person2.变更后:");System.out.println("person1:" + person1); // person1:Person@ee8f2be0{age=23, name='青花椒', student=Student@67e260bd{score=93.2, name='张三'}}System.out.println("person2:" + person2); // person2:Person@1f00b209{age=23, name='青花椒', student=Student@9853e6e6{score=88.8, name='李四'}}}
}
在深拷贝中,不只是 person1 和 person2 是不同的对象,它们中的 student1 和 student2 也是不同的对象。所以,改变 person2 中的 student2 并不会影响到 person1:
但是,通过 clone() 方法实现的深拷贝比较笨重,因为要将所有的引用类型都重写 clone() 方法,当嵌入的对象比较多的时候,就很容易出错了。
3. 序列化的方式深拷贝
利用序列化的方式进行烧烤比,序列化时将对象写到流中便于传输,而反序列化时将对象从流中读取出来。
写入流中的对象就是对原始对象的拷贝。但是需要注意,每个要序列化的类都要实现 Serializable 接口,这个接口与 Cloneable 接口类似,都是标记型接口。
继续以 Person 类和 Student 举例:
Student 类:
/*** student类** @author qiaohaojie* @date 2023/3/9 22:38*/
@Data
public class Studentl implements Serializable {private double score;private String name;public Studentl(double score, String name) {this.score = score;this.name = name;}@Overridepublic String toString() {return super.toString().substring(18) + "{" +"score=" + score +", name='" + name + '\'' +'}';}
}
Person 类:
/*** person类** @author qiaohaojie* @date 2023/3/9 22:38*/
@Data
public class Personl implements Serializable {private int age;private String name;private Studentl student;public Personl(int age, String name) {this.age = age;this.name = name;}@Overridepublic String toString() {return super.toString().substring(18) + "{" +"age=" + age +", name='" + name + '\'' +", student=" + student +'}';}//深度拷贝public Object deepClone() throws IOException, ClassNotFoundException {// 序列化ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);// 反序列化ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);return ois.readObject();}
}
Person 类也需要实现 Serializable 接口,并且在该类中,增加了一个 deepClone() 的方法,利用 OutputStream 进行序列化,InputStream 进行反序列化,这样就实现了深拷贝。
测试类:
/*** 序列化** @author qiaohaojie* @date 2023/3/9 22:41*/
public class CloneTest4 {public static void main(String[] args) throws IOException, ClassNotFoundException {Personl person1 = new Personl(23, "青花椒");Studentl student1 = new Studentl(93.2, "张三");person1.setStudent(student1);Personl person2 = (Personl) person1.deepClone();System.out.println("深拷贝后:");System.out.println("person1:" + person1); // person1:Personl@ee8f2be0{age=23, name='青花椒', student=Studentl@67e260bd{score=93.2, name='张三'}}System.out.println("person2:" + person2); // person2:Personl@1f00b209{age=23, name='青花椒', student=Studentl@9853e6e6{score=93.2, name='张三'}}Studentl student2 = person2.getStudent();student2.setName("李四");student2.setScore(88.8);System.out.println("person2.变更后:");System.out.println("person1:" + person1); // person1:Personl@ee8f2be0{age=23, name='青花椒', student=Studentl@67e260bd{score=93.2, name='张三'}}System.out.println("person2:" + person2); // person2:Personl@1f00b209{age=23, name='青花椒', student=Studentl@9853e6e6{score=88.8, name='李四'}}}
}
这种方式也可以实现深拷贝,但是由于是序列化涉及到输入流和输出流的读写,在性能上要比 HotSpot 虚拟机实现的 clone() 方法差很多。
相关文章:

Java 中的浅拷贝和深拷贝
无论是浅拷贝还是深拷贝,都可以通过 Object 类的 clone() 方法来完成: /*** 拷贝** author qiaohaojie* date 2023/3/5 15:58*/ public class CloneTest {public static void main(String[] args) throws Exception {Person person1 new Person(23, &…...

【java】 java开发中 常遇到的各种难点 思路方案
文章目录逻辑删除如何建立唯一索引唯一索引失效问题加密字段模糊查询问题maven依赖冲突问题(jar包版本冲突问题)sql in条件查询时 将结果按照传入顺序排序作为一个开发人员 总会遇到各种难题 本文列举博主 遇见/想到 的例子 ,也希望同学们可以…...

ViewBinding 和 DataBinding的使用
1.ViewBinding:视图绑定 通过视图绑定功能,您可以更轻松地编写可与视图交互的代码。在模块中启用视图绑定之后,系统会为该模块中的每个 XML 布局文件生成一个绑定类。绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。在大多数情况下&…...
HTML+CSS入门
CSS概述 CSS指层叠样式表 (Cascading Style Sheets),用来定义HTML网页中的内容用什么样式来显示。 HTML: 指定网页显示的内容 CSS: 指定内容显示的样式CSS入门案例 <html><head><meta charset"UTF-8"><title>入门案例</tit…...

【Vue】vue2导出页面内容为pdf文件,自定义选中页面内容导出为pdf文件,打印选中页面内容,预览打印内容
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、安装html2canvas和jspdf二、导出pdf使用步骤1.在utils文件夹下创建htmlToPdf.js2.在main.js中引入3.在页面中使用三、打印预览1. 引入print-js2.页面中impor…...

保姆级使用PyTorch训练与评估自己的Replknet网络教程
文章目录前言0. 环境搭建&快速开始1. 数据集制作1.1 标签文件制作1.2 数据集划分1.3 数据集信息文件制作2. 修改参数文件3. 训练4. 评估5. 其他教程前言 项目地址:https://github.com/Fafa-DL/Awesome-Backbones 操作教程:https://www.bilibili.co…...

1/4车、1/2车、整车悬架PID控制仿真合集
目录 前言 1. 1/4悬架系统 1.1数学模型 1.2仿真分析 2. 1/2悬架系统 2.1数学模型 2.2仿真模型 2.3仿真分析 3. 整车悬架系统 3.1数学模型 3.2仿真分析 参考文献 前言 前面几篇文章介绍了LQR、SkyHook、H2/H∞控制,接下来会继续介绍滑模、反步法、MPC、…...

媒体邀约的形式和步骤
传媒如春雨,润物细无声,大家好,我是51媒体网胡老师。 做媒体服务很多年,今天就与大家分享下媒体邀约都有哪些形式: 1,电话邀约:通过电话与媒体记者进行沟通,邀请其参加活动或接受采…...

Unity合批处理
一.静态合批标记为Batching Static的物体(标记后物体运行不能移动、旋转、缩放)在使用相同材质球的条件下在项目打包的时候unity会自动将这些物体合并到一个大Mesh*缺点打包后体积增大运行时内存占用增大二.动态批处理不超过300个顶点不超过900个属性不包…...

Android 进阶——Binder IPC之Native 服务的启动及代理对象的获取详解(六)
文章大纲引言一、Binder线程池的启动1、ProcessState#startThreadPool函数来启动线程池2、IPCThreadState#joinThreadPool 将当前线程进入到线程池中去等待和处理IPC请求二、Service 代理对象的获取1、获取Service Manager 代理对象BpServiceManager2、调用BpServiceManager#ge…...

企业官网怎么做?
企业官网是企业展示形象和吸引潜在客户的重要渠道之一,因此如何打造一款优秀的企业官网显得尤为重要。本文将从策划、设计、开发和上线等方面,为您介绍企业官网的制作步骤。 一、策划 1.明确目标 企业官网的制作需要明确目标,即确定官网的主…...

FPGA和IC设计怎么选?哪个发展更好?
很多人纠结FPGA和IC设计怎么选,其实往小了说,要看你选择的具体是哪个方向岗位。往大了说,将来你要是走更远,要成为大佬,那基本各个方向的都要有涉及的。 不同方向就有不同的发展,目前在薪资上IC设计要比FP…...

宁盾目录成功对接Coremail邮箱,为其提供LDAP统一认证和双因子认证
近日,宁盾与 Coremail 完成兼容适配,在 LDAP 目录用户同步、统一身份认证及双因子认证等模块成功对接。借此机会,双方将加深在产品、解决方案等多个领域的合作,携手共建信创合作生态,打造信创 LDAP 身份目录服务新样本…...
Go: struct 结构体类型和指针【学习笔记记录】
struct 结构体类型和指针struct 结构体类型1. 定义结构体2. 访问结构体成员3. 结构体的使用及匿名字段指针1. 指针变量的声明及使用2. 指针数组的定义及使用3. 函数传参修改值struct 结构体类型 Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项…...

量化派递交上市申请,数字经济风口上开启“狂飙”模式
今年全国两会,代表委员们纷纷围绕“中小企业数字化转型”建言献策。如全国政协委员、甘肃省工业和信息化厅副厅长黄宝荣建议,在工业领域加快数字经济立法,支撑中小企业数字化转型;全国政协委员、中国财政科学研究院院长刘尚希建议…...
Linux:IO接口
目录系统调用接口文件描述符一、open二、write三、read四、lseek五、close之前介绍了IO库函数,本文主要介绍系统提供的IO接口,与IO库函数搭配食用效果更佳。 系统调用接口 常使用的IO系统调用接口如下: 接口作用open打开指定的文件write向指…...

cron表达式?
简单理解corn表达式:在使用定时调度任务的时候,我们最常用的,就是cron表达式了。通过cron表达式来指定任务在某个时间点或者周期性的执行。cron表达式配置起来简洁方便,无论是Spring的Scheduled还是用Quartz框架,都支持…...

日常任务开发系统
简介 要求 1、人员信息管理:姓名、性别、出生年月、职称、学位、学习或承担的课 2、任务发布模块: 1)任务信息至少需包含:任务名称、任务类型、任务开始时间、任务截至时间、任务需要的人数、任务分值,是否需要提交任务成果等字段 2)可指定任务申领人…...

SQLMap安装教程
注意:在python3环境下安装sqlmap的时候会提示需要在python2的环境下才能安装,其实在python3.6以后也都支持sqlmap了。 sqlmap安装步骤: 一、下载python; 下载地址 https://www.python.org/downloads/ 下载教程参考(…...
【每日一题】蓝桥杯Day06
文章目录一、星期计算1、问题描述2、思路解析3、AC代码4、代码解析二、考勤刷卡1、问题描述2、解题思路3、AC代码4、代码解析5、算法分析三、卡片1、问题描述2、解题思路3、AC代码4、代码解析5、算法分析一、星期计算 原题链接:星期计算 1、问题描述 本题为填空题&a…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程
STM32F1 本教程使用零知标准板(STM32F103RBT6)通过I2C驱动ICM20948九轴传感器,实现姿态解算,并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化,适合嵌入式及物联网开发者。在基础驱动上新增…...

车载诊断架构 --- ZEVonUDS(J1979-3)简介第一篇
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…...

【阅读笔记】MemOS: 大语言模型内存增强生成操作系统
核心速览 研究背景 研究问题:这篇文章要解决的问题是当前大型语言模型(LLMs)在处理内存方面的局限性。LLMs虽然在语言感知和生成方面表现出色,但缺乏统一的、结构化的内存架构。现有的方法如检索增强生成(RA…...

Ubuntu 安装 Mysql 数据库
首先更新apt-get工具,执行命令如下: apt-get upgrade安装Mysql,执行如下命令: apt-get install mysql-server 开启Mysql 服务,执行命令如下: service mysql start并确认是否成功开启mysql,执行命令如下&am…...
RK3568项目(七)--uboot系统之外设与PMIC详解
目录 一、引言 二、按键 ------>2.1、按键种类 ------------>2.1.1、RESET ------------>2.1.2、UPDATE ------------>2.1.3、PWRON 部分 ------------>2.1.4、RK809 PMIC ------------>2.1.5、ADC按键 ------------>2.1.6、ADC按键驱动 ------…...

[electron]预脚本不显示内联script
script-src self 是 Content Security Policy (CSP) 中的一个指令,它的作用是限制加载和执行 JavaScript 脚本的来源。 具体来说: self 表示 当前源。也就是说,只有来自当前网站或者当前页面所在域名的 JavaScript 脚本才被允许执行。"…...