Java 中的浅拷贝和深拷贝

article/2025/10/5 13:21:23

无论是浅拷贝还是深拷贝,都可以通过 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='青花椒'}System.out.println("person2:" + person2); // person2:Person{age=23, name='青花椒'}}
}

扒一下 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("程序猿");person1.swtAge(66);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();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=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() 方法差很多。


http://chatgpt.dhexx.cn/article/s5tKmwKd.shtml

相关文章

Java浅拷贝和深拷贝的方式

文章目录 1. 前言2. 概念介绍2.1 拷贝 / 克隆的概念2.2 为什么需要拷贝方法?2.3 什么是浅拷贝?浅拷贝和深拷贝的区别是什么? 3. 深拷贝的实现方式3.1 手动深拷贝3.2 序列化方式3.2.1 自定义序列化工具函数3.2.2 commons-lang3 的序列化工具类…

JS 浅拷贝和深拷贝详解(巨详细)

目录 一、前置知识详解 1.1.JavaScript数据类型 1.2.理解传值和传址 二、浅拷贝 2.1.浅拷贝的定义和原理 2.2.实现浅拷贝的方法 2.2.1.手写递归实现 2.2.2.利用展开语法实现浅拷贝 2.2.3.Object.assign进行对象的合并 2.2.4.利用Array.prototype.,slice() 2.2.5.利用…

SV中的浅拷贝和深拷贝

1.浅拷贝 浅拷贝: 只拷贝对象中的数据变量,而对于对象中的数据操作(一般为任务和函数)和其中定义的其他类的句柄,采用类似“引用”的方式,浅拷贝前后共用同一内存空间。 可以使用new操作符进行复制。如果一个类包含指向另一个类的…

什么是浅拷贝和深拷贝?

文章目录 1、什么是浅拷贝?2、什么是深拷贝?2.1、常见的深拷贝方式 在Java语言中,当我们需要拷贝一个Java对象的时候,常见的会有两种方式的拷贝:浅拷贝与深拷贝。 浅拷贝:只是拷贝了源对象的地址,所以源对象…

基于java实现浅拷贝和深拷贝

目录 1、概念2、浅拷贝2.1、浅拷贝实战 3、深拷贝3.1、嵌套 clone 方法3.2、使用序列化流3.3、使用开源工具类 1、概念 浅拷贝:在拷贝一个对象时,复制基本数据类型的成员变量,但对引用数据类型的成员变量只进行引用的传递(复制其…

js实现浅拷贝和深拷贝

一.数据类型 数据分为基本数据类型和引用数据类型 基本数据类型(String, Number, Boolean, Null, Undefined,Symbol) 引用数据类型(Object[Array属于Object]) 基本数据类型的特点:直接存储在栈(stack)中的数据 引用数据类型的特点…

浅拷贝和深拷贝

最近学习string类的时候感觉这里概念有点混淆,浅拷贝与深拷贝的区别,上网查了一下,原来没我想 的难么复杂,以下是我的理解,如果有不对的地方求大佬留言交流交流。 浅拷贝: 顾名思义就是浅层的拷贝&#xff…

理解浅拷贝和深拷贝以及实现方法

一、数据类型 数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和引用数据类型Object,包含(function,Array,Date)。 1、基本数据类型的特点:直接存储在栈内存中的数据 …

VUE浅拷贝和深拷贝

文章目录 前言一、数据类型1.1.基本数据类型1.2.引用数据类型1.3.区别 二、浅拷贝2.1.定义2.2.浅拷贝特点 三、深拷贝3.1.定义3.2.深拷贝特点 四、拷贝实现方案4.1.Object.assign()4.2.concat()4.3.slice()4.4.JSON.parse(JSON.stringify())4.5.cloneDeep() 五、结论 前言 在理…

C# 中的浅拷贝和深拷贝

在本文中,将通过示例讨论C#中的浅拷贝和深拷贝。这是上一篇文章的续篇。因此,在继续本文之前,请阅读以前的文章,其中之前使用示例讨论了C#中的原型设计模式。 什么是深拷贝和浅拷贝? 浅复制和…

JAVA 浅拷贝和深拷贝

拷贝 拷贝即对已有的数据创建一个副本,在 Java 中,拷贝可分为引用拷贝、浅拷贝、深拷贝。 引用拷贝 在 Java 中,实例化后的对象存储在堆区,而局部变量存放在局部变量表(栈)中,如: public void yinYongC…

js浅拷贝和深拷贝

1、JS数据类型 基本数据类型:Boolean、String、Number、null、undefined 引用数据类型:Object、Array、Function、RegExp、Date等 2、深拷贝与浅拷贝 深拷贝和浅拷贝都只针对引用数据类型, 浅拷贝会对对象逐个成员依次拷贝,但…

C++浅拷贝和深拷贝

1、浅拷贝 浅拷贝:又称值拷贝,将源对象的值拷贝到目标对象中去,本质上来说源对象和目标对象共用一份实体,只是所引用的变量名不同,地址其实还是相同的。 举个简单的例子,你的小名叫西西,大名叫…

彻底理解Python中浅拷贝和深拷贝的区别

目录 前言 1. 浅拷贝和深拷贝的概念 2. is和的区别 3. 赋值操作 4. copy模块里面的copy()方法 5. copy模块里面的deepcopy()方法 6.字典自带的copy方法 7.切片表达式拷贝 8.总结 前言 Python 的所有变量其实都是指向内存中的对象的一个指针,这确实和之前学…

如何理解java的回调函数?

对于技术问题,会用是一回事,理解这个技术问题的来龙去脉、设计者当初为什么要设计这个功能、这个技术问题有哪些优势、适用哪些场景又是另外回事了。 前者照猫画虎得其形,后者形神兼备得其意,这也是所谓青铜与王者的区别。 会使…

java使用回调函数

java回调函数 回调函数(callback Function),顾名思义就是用来回调的函数。在两个类A、B中,A在调用B接口的同时B也在调用A 回调函数也常用于线程中的异步获取消息。 举个简单的例子,公司中老板分发任务给员工A&#…

java中回调函数的实现

在java的事务中,有时候可能会遇到以下情况,第一步是更新某某表,中间可能要更新不确定的多步,最后一步是更新缓存,结构大致如下: (1)updateA(); (2)updateXX…

什么是java回调函数

回调函数 一:故事背景二:概念三:回调函数的作用四:java中如何进行回调4.1 类图4.2 定义回调接口4.3 实现回调接口4.4 调用方法使用回调函数4.5 Main函数调用4.6 总结描述 五:回调函数的优点5.1 灵活性5.2 解耦性5.3 异…

简单举例JAVA回调函数的实现

来自维基百科的对回调(Callback)的解释:In computer programming, a callback is any executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at a given time. This execut…

java回调函数机制

Java回调函数机制 参考了网上的一些资料,下面也做出一些总结,供初学者了解学习。 一、 概述 软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调、异步调用 。 同步调用:一…