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…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...
