java -- 深拷贝和浅拷贝的区别 & 如何实现深拷贝和浅拷贝

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. 序列化与反序列化存在一定的系统开销


  • 11
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeJiao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值