死磕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是一个分布式的,开放源码的分布式应 ...
随机推荐
- Java SE 16 新增特性
Java SE 16 新增特性 作者:Grey 原文地址:Java SE 16 新增特性 源码 源仓库: Github:java_new_features 镜像仓库: GitCode:java_new ...
- 手把手教你怎么将linux的一个函数性能搞奔溃
static struct fib_table *fib_empty_table(struct net *net){ u32 id; for (id = 1; id <= RT_TA ...
- mysql 重复执行创建表、新增字段脚本
#bigint 可重复执行创建表脚本 1 Create Table If Not Exists `库名`.`表名`( 2 字段列表 3 ) ENGINE=InnoDB DEFAULT CHARSET= ...
- 第四十三篇:Git知识(基本理论)
好家伙,最近准备考试,有点忙 首先从版本控制开始 1.版本控制(版本迭代,新的版本) 如果一个项目由多个人去开发,那么总会需要去管理版本 你更一点,我更一点,一冲突,这个项目就炸了 所以需要版本控制. ...
- KingbaseES V8R6C6备份恢复案例之---单实例sys_baackup.sh备份
案例说明: KingbaseES V8R6C6中sys_backup.sh在通用机单实例环境,默认需要通过securecmdd工具以及kingbase和root用户之间的ssh互信,执行备份初始化(i ...
- Markdown Support
Markdown 支持一览 Markdown 支持一览 身正不怕影子斜 我实在没有说过这样一句话 -- 鲁迅 古代文学史发展脉络 唐诗 宋词 元曲 冯·诺依曼结构 运算器 控制器 存储器 输入输出设备 ...
- 来点基础的练习题吧,看见CSDN这类基础的代码不多
来点基础的练习题吧,看见CSDN这类基础的代码不多 //正三角形 void ex03(){ int i,k=0, rows, space; printf("请输入三角形的层次:") ...
- Openstack neutron:云数据中心底层网络架构
目录 - 目录 - 云数据中心流量类型 - NSX整体网络结构 - 管理网络(API网络) - 租户网络 - 外联网络 - 存储网络 - openstack整体网络结构 - 管理网络:(上图中蓝线) ...
- python自动更新pom文件
前言 项目越来越多,版本管理越来越麻烦,在项目上我使用 maven version 来进行版本管理.主要还是在分布式项目中模块众多的场景中使用,毕竟各个模块对外的版本需要保持统一. 关于这个插件如何使 ...
- 有偿提供ES 7.X和8.X 版本 的白金版和企业版 使用咨询服务
若有意向,可通过下方的邮箱发邮件进行咨询,非诚勿扰.. 邮箱地址:sandu12345@msn.cn