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

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='程序猿'}}
}

步骤大概是这样的:

  1. 通过 new 关键字声明了一个 Person 对象,并将其赋值给 person1;
  2. 调用 clone() 方法进行对象的拷贝,并将其赋值给 person2;
  3. 打印 person1 和 person2;
  4. 将 person2 的 name 字段修改为 “程序猿”;
  5. 打印 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='李四'}}}
}

步骤大概是这样的:

  1. 通过 new 关键字声明了一个 Person 对象,并将其赋值给 person1;
  2. 通过 new 关键字声明了一个 Stident 对象,并将其赋值给 student1;
  3. 把 person1 的 student 字段设置为 student1;
  4. 调用 clone() 方法进行对象的拷贝,并将其赋值给 person2;
  5. 打印 person1 和 person2;
  6. 获取 person2 的 student 字段,并将其赋值给 student2;
  7. 把 student2 的 name 字段修改为 “李四”,score 字段修改为 88.8;
  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)&#xff0c;用来定义HTML网页中的内容用什么样式来显示。 HTML: 指定网页显示的内容 CSS: 指定内容显示的样式CSS入门案例 <html><head><meta charset"UTF-8"><title>入门案例</tit…...

【Vue】vue2导出页面内容为pdf文件,自定义选中页面内容导出为pdf文件,打印选中页面内容,预览打印内容

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、安装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. 其他教程前言 项目地址&#xff1a;https://github.com/Fafa-DL/Awesome-Backbones 操作教程&#xff1a;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∞控制&#xff0c;接下来会继续介绍滑模、反步法、MPC、…...

媒体邀约的形式和步骤

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

Unity合批处理

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

Android 进阶——Binder IPC之Native 服务的启动及代理对象的获取详解(六)

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

企业官网怎么做?

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

FPGA和IC设计怎么选?哪个发展更好?

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

宁盾目录成功对接Coremail邮箱,为其提供LDAP统一认证和双因子认证

近日&#xff0c;宁盾与 Coremail 完成兼容适配&#xff0c;在 LDAP 目录用户同步、统一身份认证及双因子认证等模块成功对接。借此机会&#xff0c;双方将加深在产品、解决方案等多个领域的合作&#xff0c;携手共建信创合作生态&#xff0c;打造信创 LDAP 身份目录服务新样本…...

Go: struct 结构体类型和指针【学习笔记记录】

struct 结构体类型和指针struct 结构体类型1. 定义结构体2. 访问结构体成员3. 结构体的使用及匿名字段指针1. 指针变量的声明及使用2. 指针数组的定义及使用3. 函数传参修改值struct 结构体类型 Go 语言中数组可以存储同一类型的数据&#xff0c;但在结构体中我们可以为不同项…...

量化派递交上市申请,数字经济风口上开启“狂飙”模式

今年全国两会&#xff0c;代表委员们纷纷围绕“中小企业数字化转型”建言献策。如全国政协委员、甘肃省工业和信息化厅副厅长黄宝荣建议&#xff0c;在工业领域加快数字经济立法&#xff0c;支撑中小企业数字化转型&#xff1b;全国政协委员、中国财政科学研究院院长刘尚希建议…...

Linux:IO接口

目录系统调用接口文件描述符一、open二、write三、read四、lseek五、close之前介绍了IO库函数&#xff0c;本文主要介绍系统提供的IO接口&#xff0c;与IO库函数搭配食用效果更佳。 系统调用接口 常使用的IO系统调用接口如下&#xff1a; 接口作用open打开指定的文件write向指…...

cron表达式?

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

日常任务开发系统

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

SQLMap安装教程

注意&#xff1a;在python3环境下安装sqlmap的时候会提示需要在python2的环境下才能安装&#xff0c;其实在python3.6以后也都支持sqlmap了。 sqlmap安装步骤&#xff1a; 一、下载python&#xff1b; 下载地址 https://www.python.org/downloads/ 下载教程参考&#xff08…...

【每日一题】蓝桥杯Day06

文章目录一、星期计算1、问题描述2、思路解析3、AC代码4、代码解析二、考勤刷卡1、问题描述2、解题思路3、AC代码4、代码解析5、算法分析三、卡片1、问题描述2、解题思路3、AC代码4、代码解析5、算法分析一、星期计算 原题链接&#xff1a;星期计算 1、问题描述 本题为填空题&a…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中&#xff0c;高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术&#xff0c;实现年省电费15%-60%&#xff0c;且不改动原有装备、安装快捷、…...

基础测试工具使用经验

背景 vtune&#xff0c;perf, nsight system等基础测试工具&#xff0c;都是用过的&#xff0c;但是没有记录&#xff0c;都逐渐忘了。所以写这篇博客总结记录一下&#xff0c;只要以后发现新的用法&#xff0c;就记得来编辑补充一下 perf 比较基础的用法&#xff1a; 先改这…...

【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验

系列回顾&#xff1a; 在上一篇中&#xff0c;我们成功地为应用集成了数据库&#xff0c;并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了&#xff01;但是&#xff0c;如果你仔细审视那些 API&#xff0c;会发现它们还很“粗糙”&#xff1a;有…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...