深拷贝和浅拷贝的区别 & 如何实现深拷贝和浅拷贝
Posted Z && Y
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深拷贝和浅拷贝的区别 & 如何实现深拷贝和浅拷贝相关的知识,希望对你有一定的参考价值。
1. 深拷贝和浅拷贝的区别
深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。
假设B复制了A,修改A的时候,看B是否发生变化:
如果B跟着也变了,说明是浅拷贝(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝(修改堆内存中的不同的值)
- 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
- 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
- 使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
1.1 浅拷贝实例
1.1.1 测试1 直接赋值
public class Student {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) {
Student student1 = new Student("Tom");
Student student2 = student1;
// 因为是指向同一个地址 所以结果必然是相同的
System.out.println(student1 == student2);
}
}
1.1.2 测试2 改变源对象的值
public class Student {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) {
Student student1 = new Student("Tom");
Student student2 = student1;
student1.setName("Jack");
System.out.println(student2.getName());
}
}
运行结果:
1.2 深拷贝实例
这是用于深拷贝的测试类
public class Student {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
1.2.1 方法一: 构造函数
@org.junit.Test
public void constructorCopy() {
// 被克隆的类
Student xiaoMing = new Student("小明");
// 调用构造函数时进行深拷贝
Student cloneStudent = new Student(xiaoMing.getName());
System.out.println(xiaoMing == cloneStudent); // false
}
1.2.2 方法二: 重载clone()方法
Object类有个clone()的拷贝方法,不过它是protected类型的,我们需要重写它并修改为public类型。除此之外,子类还需要实现Cloneable接口来告诉JVM这个类是可以拷贝的。
让我们修改一下Student类,实现Cloneable接口,使其支持深拷贝。
Student.java
public class Student implements Cloneable {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 重写克隆方法
@Override
public Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}
}
测试:
@org.junit.Test
public void coneableCopy() throws CloneNotSupportedException {
// 被克隆的类
Student xiaoMing = new Student("小明");
Student cloneStudent = xiaoMing.clone();
System.out.println(xiaoMing == cloneStudent);
}
1.2.3 方法三:Apache Commons Lang序列化
Java提供了序列化的能力,我们可以先将源对象进行序列化,再反序列化生成拷贝对象。但是,使用序列化的前提是拷贝的类(包括其成员变量)需要实现Serializable接口。Apache Commons Lang包对Java序列化进行了封装,我们可以直接使用它。
第一步:导入依赖
pom.xml
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
第二步:让我们修改一下Student类,实现Serializable接口,使其支持序列化。
public class Student implements Serializable {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试:
@org.junit.Test
public void coneableCopy() throws CloneNotSupportedException {
// 被克隆的类
Student xiaoMing = new Student("小明");
// 使用Apache Commons Lang序列化进行深拷贝
Student copyStudent = (Student) SerializationUtils.clone(xiaoMing);
System.out.println(xiaoMing == copyStudent);
}
1.2.4 方法四:Gson
Gson可以将对象序列化成JSON,也可以将JSON反序列化成对象,所以我们可以用它进行深拷贝。
第一步:导入依赖
pom.xml
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
第二步: 修改一下Student类,实现Serializable接口,使其支持序列化。同上
测试:
@org.junit.Test
public void coneableCopy() throws CloneNotSupportedException {
// 被克隆的类
Student xiaoMing = new Student("小明");
// 使用Gson序列化进行深拷贝
Gson gson = new Gson();
Student copyStudent = gson.fromJson(gson.toJson(xiaoMing), Student.class);
System.out.println(xiaoMing == copyStudent);
}
1.2.5 方法五: Jackson序列化
Jackson与Gson相似,可以将对象序列化成JSON,明显不同的地方是拷贝的类(包括其成员变量)需要有默认的无参构造函数
第一步:导入依赖
pom.xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
第二步:让我们修改一下Student类,实现默认的无参构造函数,使其支持Jackson。
Student.java
package com.tian.pojo;
import java.io.Serializable;
/**
* ClassName: Student
* Description:
*
* @author Tianjiao
* @date 2021/5/30 18:12
*/
public class Student implements Serializable {
private String name;
// 实现无参构造方法
public Student() {
}
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试:
@org.junit.Test
public void coneableCopy() throws CloneNotSupportedException, JsonProcessingException {
// 被克隆的类
Student xiaoMing = new Student("小明");
// 使用Jackson序列化进行深拷贝
ObjectMapper objectMapper = new ObjectMapper();
Student copyStudent = objectMapper.readValue(objectMapper.writeValueAsString(xiaoMing), Student.class);
System.out.println(xiaoMing == copyStudent);
}
1.2.6 小结
深拷贝方法 | 优点 | 缺点 |
---|---|---|
构造函数 | 1. 底层实现简单 2. 不需要引入第三方包 3. 系统开销小 4. 对拷贝类没有要求,不需要实现额外接口和方法 | 1. 可用性差,每次新增成员变量都需要新增新的拷贝构造函数 |
重载clone()方法 | 1. 底层实现较简单 2. 不需要引入第三方包 3. 系统开销小 | 1. 可用性较差,每次新增成员变量可能需要修改clone()方法 2. 拷贝类(包括其成员变量)需要实现Cloneable接口 |
Apache Commons Lang序列化 | 1. 可用性强,新增成员变量不需要修改拷贝方法 | 1. 底层实现较复杂 2. 需要引入Apache Commons Lang第三方JAR包 3. 拷贝类(包括其成员变量)需要实现Serializable接口 4. 序列化与反序列化存在一定的系统开销 |
Gson序列化 | 1. 可用性强,新增成员变量不需要修改拷贝方法 2. 对拷贝类没有要求,不需要实现额外接口和方法 | 1. 底层实现复杂 2. 需要引入Gson第三方JAR包 3. 序列化与反序列化存在一定的系统开销 |
Jackson序列化 | 1. 可用性强,新增成员变量不需要修改拷贝方法 | 1. 底层实现复杂 2. 需要引入Jackson第三方JAR包 3. 拷贝类(包括其成员变量)需要实现默认的无参构造函数 4. 序列化与反序列化存在一定的系统开销 |
以上是关于深拷贝和浅拷贝的区别 & 如何实现深拷贝和浅拷贝的主要内容,如果未能解决你的问题,请参考以下文章