• 死磕Java面试系列:深拷贝与浅拷贝的实现原理


    深拷贝与浅拷贝的问题,也是面试中的常客。虽然大家都知道两者表现形式不同点在哪里,但是很少去深究其底层原理,也不知道怎么才能优雅的实现一个深拷贝。其实工作中也常常需要实现深拷贝,今天小编就带大家一块深入剖析一下深拷贝与浅拷贝的实现原理,并手把手教你怎么优雅的实现深拷贝。

    1. 什么是深拷贝与浅拷贝

    浅拷贝: 只拷贝栈内存中的数据,不拷贝堆内存中数据。

    深拷贝: 既拷贝栈内存中的数据,又拷贝堆内存中的数据。

    2. 浅拷贝的实现原理

    由于浅拷贝只拷贝了栈内存中数据,栈内存中存储的都是基本数据类型,堆内存中存储了数组、引用数据类型等。

    使用代码验证一下:

    想要实现clone功能,需要实现 Cloneable 接口,并重写 clone 方法。

    1. 先创建一个用户类
    1. // 用户的实体类,用作验证
    2. public class User implements Cloneable {
    3. private String name;
    4. // 每个用户都有一个工作
    5. private Job job;
    6. public String getName() {
    7. return name;
    8. }
    9. public void setName(String name) {
    10. this.name = name;
    11. }
    12. public Job getJob() {
    13. return job;
    14. }
    15. public void setJob(Job job) {
    16. this.job = job;
    17. }
    18. @Override
    19. public User clone() throws CloneNotSupportedException {
    20. User user = (User) super.clone();
    21. return user;
    22. }
    23. }
    1. 再创建一个工作类
    1. // 工作的实体类,并没有实现Cloneable接口
    2. public class Job {
    3. private String content;
    4. public String getContent() {
    5. return content;
    6. }
    7. public void setContent(String content) {
    8. this.content = content;
    9. }
    10. }
    1. 测试浅拷贝
    1. /**
    2. * @author 一灯架构
    3. * @apiNote Java浅拷贝示例
    4. **/
    5. public class Demo {
    6. public static void main(String[] args) throws CloneNotSupportedException {
    7. // 1. 创建用户对象,{"name":"一灯架构","job":{"content":"开发"}}
    8. User user1 = new User();
    9. user1.setName("一灯架构");
    10. Job job1 = new Job();
    11. job1.setContent("开发");
    12. user1.setJob(job1);
    13. // 2. 拷贝用户对象,name修改为"张三",工作内容修改"测试"
    14. User user2 = user1.clone();
    15. user2.setName("张三");
    16. Job job2 = user2.getJob();
    17. job2.setContent("测试");
    18. // 3. 输出结果
    19. System.out.println("user原对象= " + user1);
    20. System.out.println("user拷贝对象= " + user2);
    21. }
    22. }

    输出结果:

    1. user原对象= {"name":"一灯架构","job":{"content":"测试"}}
    2. user拷贝对象= {"name":"张三","job":{"content":"测试"}}

    从结果中可以看出,对象拷贝把name修改为”张三“,原对象并没有变,name是String类型,是基本数据类型,存储在栈内存中。对象拷贝了一份新的栈内存数据,修改并不会影响原对象。

    然后对象拷贝把Job中content修改为”测试“,原对象也跟着变了,原因是Job是引用类型,存储在堆内存中。对象拷贝和原对象指向的同一个堆内存的地址,所以修改会影响到原对象。

    3. 深拷贝的实现原理

    深拷贝是既拷贝栈内存中的数据,又拷贝堆内存中的数据。

    实现深拷贝有很多种方法,下面就详细讲解一下,看使用哪种方式更方便快捷。

    3.1 实现Cloneable接口

    通过实现Cloneable接口来实现深拷贝是最常见的。

    想要实现clone功能,需要实现Cloneable接口,并重写clone方法。

    1. 先创建一个用户类
    1. // 用户的实体类,用作验证
    2. public class User implements Cloneable {
    3. private String name;
    4. // 每个用户都有一个工作
    5. private Job job;
    6. public String getName() {
    7. return name;
    8. }
    9. public void setName(String name) {
    10. this.name = name;
    11. }
    12. public Job getJob() {
    13. return job;
    14. }
    15. public void setJob(Job job) {
    16. this.job = job;
    17. }
    18. @Override
    19. public User clone() throws CloneNotSupportedException {
    20. User user = (User) super.clone();
    21. // User对象中所有引用类型属性都要执行clone方法
    22. user.setJob(user.getJob().clone());
    23. return user;
    24. }
    25. }
    1. 再创建一个工作类
    1. // 工作的实体类,需要实现Cloneable接口
    2. public class Job implements Cloneable {
    3. private String content;
    4. public String getContent() {
    5. return content;
    6. }
    7. public void setContent(String content) {
    8. this.content = content;
    9. }
    10. @Override
    11. protected Job clone() throws CloneNotSupportedException {
    12. return (Job) super.clone();
    13. }
    14. }
    1. 测试浅拷贝
    1. /**
    2. * @author 一灯架构
    3. * @apiNote Java深拷贝示例
    4. **/
    5. public class Demo {
    6. public static void main(String[] args) throws CloneNotSupportedException {
    7. // 1. 创建用户对象,{"name":"一灯架构","job":{"content":"开发"}}
    8. User user1 = new User();
    9. user1.setName("一灯架构");
    10. Job job1 = new Job();
    11. job1.setContent("开发");
    12. user1.setJob(job1);
    13. // 2. 拷贝用户对象,name修改为"张三",工作内容修改"测试"
    14. User user2 = user1.clone();
    15. user2.setName("张三");
    16. Job job2 = user2.getJob();
    17. job2.setContent("测试");
    18. // 3. 输出结果
    19. System.out.println("user原对象= " + user1);
    20. System.out.println("user拷贝对象= " + user2);
    21. }
    22. }

    输出结果:

    1. user原对象= {"name":"一灯架构","job":{"content":"开发"}}
    2. user拷贝对象= {"name":"张三","job":{"content":"测试"}}

    从结果中可以看出,user拷贝对象修改了name属性和Job对象中内容,都没有影响到原对象,实现了深拷贝。

    通过实现Cloneable接口的方式来实现深拷贝,是Java中最常见的实现方式。

    缺点是: 比较麻烦,需要所有实体类都实现Cloneable接口,并重写clone方法。如果实体类中新增了一个引用对象类型的属性,还需要添加到clone方法中。如果继任者忘了修改clone方法,相当于挖了一个坑。

    3.2 使用JSON字符串转换

    实现方式就是:

    1. 先把user对象转换成json字符串
    2. 再把json字符串转换成user对象

    这是个偏方,但是偏方治大病,使用起来非常方便,一行代码即可实现。

    下面使用fastjson实现,使用Gson、Jackson也是一样的:

    1. import com.alibaba.fastjson.JSON;
    2. /**
    3. * @author 一灯架构
    4. * @apiNote Java深拷贝示例
    5. **/
    6. public class Demo {
    7. public static void main(String[] args) throws CloneNotSupportedException {
    8. // 1. 创建用户对象,{"name":"一灯架构","job":{"content":"开发"}}
    9. User user1 = new User();
    10. user1.setName("一灯架构");
    11. Job job1 = new Job();
    12. job1.setContent("开发");
    13. user1.setJob(job1);
    14. 2. 拷贝用户对象,name修改为"张三",工作内容修改"测试"
    15. User user2 = JSON.parseObject(JSON.toJSONString(user1), User.class);
    16. user2.setName("张三");
    17. Job job2 = user2.getJob();
    18. job2.setContent("测试");
    19. // 3. 输出结果
    20. System.out.println("user原对象= " + JSON.toJSONString(user1));
    21. System.out.println("user拷贝对象= " + JSON.toJSONString(user2));
    22. }
    23. }

    输出结果:

    1. user原对象= {"name":"一灯架构","job":{"content":"开发"}}
    2. user拷贝对象= {"name":"张三","job":{"content":"测试"}}

    从结果中可以看出,user拷贝对象修改了name属性和Job对象中内容,并没有影响到原对象,实现了深拷贝。

    3.3 集合实现深拷贝

    再说一下Java集合怎么实现深拷贝?

    其实非常简单,只需要初始化新对象的时候,把原对象传入到新对象的构造方法中即可。

    以最常用的ArrayList为例:

    1. /**
    2. * @author 一灯架构
    3. * @apiNote Java深拷贝示例
    4. **/
    5. public class Demo {
    6. public static void main(String[] args) throws CloneNotSupportedException {
    7. // 1. 创建原对象
    8. List<User> userList = new ArrayList<>();
    9. // 2. 创建深拷贝对象
    10. List<User> userCopyList = new ArrayList<>(userList);
    11. }
    12. }
  • 相关阅读:
    面试必问 | 一个线程从创建到消亡要经历哪些阶段?
    火山引擎 DataLeap:一家企业,数据体系要怎么搭建?
    Conan安装第三方依赖库时SSL验证失败解决办法
    1. 深度学习——激活函数
    mysql8.0卸载
    【AI资讯月刊】350+资源大盘点!6月不容错过的资料和动态,都都都在这里啦!<附下载>
    Elasticsearch7 单节点与集群部署
    Linux 系统编程,Binder 学习,文件访问相关的接口
    开源模型应用落地-业务优化篇(八)
    easyExcel使用场景
  • 原文地址:https://blog.csdn.net/m0_73311735/article/details/127729253