死磕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);
}

}


责任编辑:武晓燕 来源: 一灯架构
相关推荐

2009-05-19 17:28:44

深拷贝浅拷贝clone()

2024-03-15 15:03:23

2022-07-26 08:07:03

Python浅拷贝深拷贝

2017-08-16 13:30:05

Java深拷贝浅拷贝

2020-10-12 08:35:22

JavaScript

2023-05-17 08:42:46

深拷贝Golang

2021-01-08 06:15:09

深拷贝浅拷贝写时拷贝

2021-09-10 07:41:06

Python拷贝Python基础

2021-07-16 12:33:24

Javascript深拷贝浅拷贝

2018-09-26 14:37:17

JavaScript前端编程语言

2024-04-17 09:01:08

Python深拷贝浅拷贝

2020-06-23 08:41:47

JavaScript开发技术

2019-02-25 08:58:16

Python深拷贝浅拷贝

2024-02-05 22:56:16

C++拷贝开发

2021-09-27 11:07:11

深拷贝浅拷贝内存

2020-08-03 08:24:26

原型模式拷贝

2023-09-22 12:21:33

Python深拷贝浅拷贝

2022-09-30 15:03:09

C语言深拷贝浅拷贝

2018-05-10 14:20:18

前端JavaScript深拷贝

2021-10-18 09:01:01

前端赋值浅拷贝
点赞
收藏

51CTO技术栈公众号