死磕Java面试系列:深拷贝与浅拷贝的实现原理
深拷贝与浅拷贝的问题,也是面试中的常客。虽然大家都知道两者表现形式不同点在哪里,但是很少去深究其底层原理,也不知道怎么才能优雅的实现一个深拷贝。其实工作中也常常需要实现深拷贝,今天一灯就带大家一块深入剖析一下深拷贝与浅拷贝的实现原理,并手把手教你怎么优雅的实现深拷贝。
1. 什么是深拷贝与浅拷贝
浅拷贝: 只拷贝栈内存中的数据,不拷贝堆内存中数据。
深拷贝: 既拷贝栈内存中的数据,又拷贝堆内存中的数据。
2. 浅拷贝的实现原理
由于浅拷贝只拷贝了栈内存中数据,栈内存中存储的都是基本数据类型,堆内存中存储了数组、引用数据类型等。

使用代码验证一下:
想要实现clone功能,需要实现 Cloneable 接口,并重写 clone 方法。
- 先创建一个用户类
// 用户的实体类,用作验证
public class User implements Cloneable {
private String name;
// 每个用户都有一个工作
private Job job;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Job getJob() {
return job;
}
public void setJob(Job job) {
this.job = job;
}
@Override
public User clone() throws CloneNotSupportedException {
User user = (User) super.clone();
return user;
}
}
- 再创建一个工作类
// 工作的实体类,并没有实现Cloneable接口
public class Job {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
- 测试浅拷贝
/**
* @author 一灯架构
* @apiNote Java浅拷贝示例
**/
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException {
// 1. 创建用户对象,{"name":"一灯架构","job":{"content":"开发"}}
User user1 = new User();
user1.setName("一灯架构");
Job job1 = new Job();
job1.setContent("开发");
user1.setJob(job1);
// 2. 拷贝用户对象,name修改为"张三",工作内容修改"测试"
User user2 = user1.clone();
user2.setName("张三");
Job job2 = user2.getJob();
job2.setContent("测试");
// 3. 输出结果
System.out.println("user原对象= " + user1);
System.out.println("user拷贝对象= " + user2);
}
}
输出结果:
user原对象= {"name":"一灯架构","job":{"content":"测试"}}
user拷贝对象= {"name":"张三","job":{"content":"测试"}}
从结果中可以看出,对象拷贝把name修改为”张三“,原对象并没有变,name是String类型,是基本数据类型,存储在栈内存中。对象拷贝了一份新的栈内存数据,修改并不会影响原对象。
然后对象拷贝把Job中content修改为”测试“,原对象也跟着变了,原因是Job是引用类型,存储在堆内存中。对象拷贝和原对象指向的同一个堆内存的地址,所以修改会影响到原对象。
3. 深拷贝的实现原理
深拷贝是既拷贝栈内存中的数据,又拷贝堆内存中的数据。

实现深拷贝有很多种方法,下面就详细讲解一下,看使用哪种方式更方便快捷。
3.1 实现Cloneable接口
通过实现Cloneable接口来实现深拷贝是最常见的。
想要实现clone功能,需要实现Cloneable接口,并重写clone方法。
- 先创建一个用户类
// 用户的实体类,用作验证
public class User implements Cloneable {
private String name;
// 每个用户都有一个工作
private Job job;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Job getJob() {
return job;
}
public void setJob(Job job) {
this.job = job;
}
@Override
public User clone() throws CloneNotSupportedException {
User user = (User) super.clone();
// User对象中所有引用类型属性都要执行clone方法
user.setJob(user.getJob().clone());
return user;
}
}
- 再创建一个工作类
// 工作的实体类,需要实现Cloneable接口
public class Job implements Cloneable {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
protected Job clone() throws CloneNotSupportedException {
return (Job) super.clone();
}
}
- 测试浅拷贝
/**
* @author 一灯架构
* @apiNote Java深拷贝示例
**/
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException {
// 1. 创建用户对象,{"name":"一灯架构","job":{"content":"开发"}}
User user1 = new User();
user1.setName("一灯架构");
Job job1 = new Job();
job1.setContent("开发");
user1.setJob(job1);
// 2. 拷贝用户对象,name修改为"张三",工作内容修改"测试"
User user2 = user1.clone();
user2.setName("张三");
Job job2 = user2.getJob();
job2.setContent("测试");
// 3. 输出结果
System.out.println("user原对象= " + user1);
System.out.println("user拷贝对象= " + user2);
}
}
输出结果:
user原对象= {"name":"一灯架构","job":{"content":"开发"}}
user拷贝对象= {"name":"张三","job":{"content":"测试"}}
从结果中可以看出,user拷贝对象修改了name属性和Job对象中内容,都没有影响到原对象,实现了深拷贝。
通过实现Cloneable接口的方式来实现深拷贝,是Java中最常见的实现方式。
缺点是: 比较麻烦,需要所有实体类都实现Cloneable接口,并重写clone方法。如果实体类中新增了一个引用对象类型的属性,还需要添加到clone方法中。如果继任者忘了修改clone方法,相当于挖了一个坑。
3.2 使用JSON字符串转换
实现方式就是:
- 先把user对象转换成json字符串
- 再把json字符串转换成user对象
这是个偏方,但是偏方治大病,使用起来非常方便,一行代码即可实现。
下面使用fastjson实现,使用Gson、Jackson也是一样的:
import com.alibaba.fastjson.JSON;
/**
* @author 一灯架构
* @apiNote Java深拷贝示例
**/
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException {
// 1. 创建用户对象,{"name":"一灯架构","job":{"content":"开发"}}
User user1 = new User();
user1.setName("一灯架构");
Job job1 = new Job();
job1.setContent("开发");
user1.setJob(job1);
//// 2. 拷贝用户对象,name修改为"张三",工作内容修改"测试"
User user2 = JSON.parseObject(JSON.toJSONString(user1), User.class);
user2.setName("张三");
Job job2 = user2.getJob();
job2.setContent("测试");
// 3. 输出结果
System.out.println("user原对象= " + JSON.toJSONString(user1));
System.out.println("user拷贝对象= " + JSON.toJSONString(user2));
}
}
输出结果:
user原对象= {"name":"一灯架构","job":{"content":"开发"}}
user拷贝对象= {"name":"张三","job":{"content":"测试"}}
从结果中可以看出,user拷贝对象修改了name属性和Job对象中内容,并没有影响到原对象,实现了深拷贝。
3.3 集合实现深拷贝
再说一下Java集合怎么实现深拷贝?
其实非常简单,只需要初始化新对象的时候,把原对象传入到新对象的构造方法中即可。
以最常用的ArrayList为例:
/**
* @author 一灯架构
* @apiNote Java深拷贝示例
**/
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException {
// 1. 创建原对象
List<User> userList = new ArrayList<>();
// 2. 创建深拷贝对象
List<User> userCopyList = new ArrayList<>(userList);
}
}
我是「一灯架构」,如果本文对你有帮助,欢迎各位小伙伴点赞、评论和关注,感谢各位老铁,我们下期见

死磕Java面试系列:深拷贝与浅拷贝的实现原理的更多相关文章
- 死磕 java同步系列之AQS终篇(面试)
问题 (1)AQS的定位? (2)AQS的重要组成部分? (3)AQS运用的设计模式? (4)AQS的总体流程? 简介 AQS的全称是AbstractQueuedSynchronizer,它的定位是为 ...
- 死磕 java同步系列之redis分布式锁进化史
问题 (1)redis如何实现分布式锁? (2)redis分布式锁有哪些优点? (3)redis分布式锁有哪些缺点? (4)redis实现分布式锁有没有现成的轮子可以使用? 简介 Redis(全称:R ...
- 死磕 java同步系列之终结篇
简介 同步系列到此就结束了,本篇文章对同步系列做一个总结. 脑图 下面是关于同步系列的一份脑图,列举了主要的知识点和问题点,看过本系列文章的同学可以根据脑图自行回顾所学的内容,也可以作为面试前的准备. ...
- 死磕 java同步系列之AQS起篇
问题 (1)AQS是什么? (2)AQS的定位? (3)AQS的实现原理? (4)基于AQS实现自己的锁? 简介 AQS的全称是AbstractQueuedSynchronizer,它的定位是为Jav ...
- 死磕 java同步系列之volatile解析
问题 (1)volatile是如何保证可见性的? (2)volatile是如何禁止重排序的? (3)volatile的实现原理? (4)volatile的缺陷? 简介 volatile可以说是Java ...
- 死磕 java同步系列之自己动手写一个锁Lock
问题 (1)自己动手写一个锁需要哪些知识? (2)自己动手写一个锁到底有多简单? (3)自己能不能写出来一个完美的锁? 简介 本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁. ...
- 死磕 java同步系列之CyclicBarrier源码解析——有图有真相
问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...
- 死磕 java同步系列之Phaser源码解析
问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...
- 死磕 java同步系列之zookeeper分布式锁
问题 (1)zookeeper如何实现分布式锁? (2)zookeeper分布式锁有哪些优点? (3)zookeeper分布式锁有哪些缺点? 简介 zooKeeper是一个分布式的,开放源码的分布式应 ...
随机推荐
- Taurus.MVC 微服务框架 入门开发教程:项目部署:4、微服务应用程序发布到Docker部署(上)。
系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 开源地址:https://github.com/cyq1162/Taurus.MVC 本系列第一篇:Tauru ...
- KingbaseES 全局索引是否因为DDL操作而变为Unusable ?
前言 Oracle 在对分区做DDL操作时,会使分区全局索引失效,需要加上关键字update global indexes.KingbaseES 同样支持全局索引.那么,如果对分区表进行DDL操作,那 ...
- KingbaseES R6 集群手工配置VIP案例
经常有用户问,V8R6集群搭建时没有配置VIP,搭建完成后,如何添加VIP?以下向大家介绍下手动添加VIP 的过程. 一.操作系统环境 操作系统(UOS): root@uos01:~# cat /et ...
- vivo 全球商城:电商平台通用取货码设计
vivo官网商城开发团队 - Zhou Longjian 一.背景 随着O2O线上线下业务的不断扩展,电商平台也在逐步完善交易侧相关的产品功能.在最近的需求版本中,业务方为进一步提升用户的使用体验,规 ...
- LFS(Linux From Scratch)构建过程全记录(一):准备工作
写在前面 本人修学了一门课,名曰<操作系统课程设计>,其任务为基于LFS以编译源代码的方式制作一个基本的Linux操作系统,并且编写在linux下的GUI软件. 本操作系统构建的全过程将分 ...
- Go语言学习的坑爹历程
鄙人暑期实习,需要用Go语言进行编程 在go语言中,结构体的定义只支持变量的声明,成员函数是采用"接口方法"来实现的 留一个成员定义的模板在此 package main impor ...
- 论文解读(RvNN)《Rumor Detection on Twitter with Tree-structured Recursive Neural Networks》
论文信息 论文标题:Rumor Detection on Twitter with Tree-structured Recursive Neural Networks论文作者:Jing Ma, Wei ...
- IK分词器实现原理剖析 —— 一个小问题引发的思考
前言: 网上很多的文章都建议在使用IK分词器的时候,建立索引的时候使用ik_max_word模式:搜索的时候使用ik_smart模式.理由是max_word模式分词的结果会包含smart分词的结果,这 ...
- 痞子衡嵌入式:理解i.MXRT中FlexSPI外设lookupTable里配置访问行列混合寻址Memory的参数值
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT中FlexSPI外设lookupTable里配置访问行列混合寻址Memory的参数值. 关于 FlexSPI 外设的 loo ...
- 使用Metricbeat监控zookeeper遇到的问题
1.metricbeat中启动自动加载模块 metricbeat.config.modules: path: ${path.config}/modules.d/*.yml reload.enabled ...