大聪明教你学Java | 面试官:谈谈你对深拷贝和浅拷贝的理解

16,342 阅读6分钟

前言

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

在面试的时候,如果面试官问对你说:请谈谈你对深拷贝和浅拷贝的理解,你会怎么回答这个问题呢?可能有很多小伙伴都不太理解深拷贝和浅拷贝的含义和区别,那么今天就和各位小伙伴分享一下我对二者的理解😊。

拷贝

拷贝,顾名思义就是为了获得一个相同的对象,而不需要我们再人为的创建和赋值。Java中的对象拷贝(Object Copy)指的就是将一个对象的所有属性复制给另一个有着相同类类型的对象。咱们举个小例子👇

有两个对象,分别为对象A和对象B,同时这两个对象都属于XX类,并且对象A和对象B都具有属性a和b,那么将对象A拷贝给对象B的操作过程就是:B.a = A.a; B.b = A.b;

我们在开发应用程序的时候经常需要用到拷贝,使用拷贝的目的主要是为了在新的上下文环境中复用现有对象的部分(或全部)数据。Java 中的对象拷贝主要分为两类:浅拷贝(Shallow Copy)、深拷贝(Deep Copy)。

🍓🍓浅拷贝🍓🍓

首先我们先看一下浅拷贝的含义👇

🥝浅拷贝🥝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝。

这么说可能不太好理解,我们对这句话再做一个简单的解释。对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将这个属性值复制一份给新的对象,因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据;对于数据类型是引用数据类型的成员变量,比如说该成员变量是某个数组或某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(即内存地址)复制一份给新的对象,也就说这两个不同对象的该成员变量都指向了同一个地址,此时在一个对象中修改该成员变量就会影响到另一个对象所拷贝得到的数据。如下图所示👇

在这里插入图片描述 实现浅拷贝的方法也很简单,只需要将需要拷贝的类实现 Cloneable 接口并覆写 clone() 方法即可,我们一起看看示例代码:

/**
 * 模拟引用对象
 * @description: Subject
 * @author: 庄霸.liziye
 * @create: 2022-03-31 15:43
 **/
public class Subject {

    private String name;

    public Subject(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Subject{" +
                "name='" + name + '\'' +
                '}';
    }
}
/**
 * 人员类
 * @description: Person
 * @author: 庄霸.liziye
 * @create: 2022-03-31 15:43
 **/
public class Person implements Cloneable {
    /**
     * 引用数据类型
     */
    private Subject subject;

    /**
     * 基础数据类型
     */
    private String name;

    /**
     * 基础数据类型
     */
    private int age;

    public Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 重写clone()方法
     * @return
     */
    @Override
    public Object clone() {
        //浅拷贝
        try {
            // 直接调用父类的clone()方法
            return super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }

    @Override
    public String toString() {
        return "[Person: " + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
    }
}

/**
 * @description: Test
 * @author: 庄霸.liziye
 * @create: 2022-03-31 15:49
 **/
public class Test {
    public static void main(String[] args) {
        Subject subject = new Subject("张三");
        Person personA = new Person();
        personA.setSubject(subject);
        personA.setName("李四");
        personA.setAge(20);

        Person personB = (Person) personA.clone();
        personB.setName("王五");
        personB.setAge(18);
        Subject subjectB = personB.getSubject();
        subjectB.setName("赵六");
        System.out.println("PersonA:" + personA.toString());
        System.out.println("PersonB:" + personB.toString());
    }
}

在这里插入图片描述 我们通过运行结果就能看出,PersonB 是通过 personA.clone() 拷贝后得到的 ,但是 personA 和 personB 是两个不同的对象。personA 和 personB 的基础数据类型的修改互不影响,而引用类型 subject 的值被修改后,另一个对象的 subject 也随之发生改变。

🍓🍓深拷贝🍓🍓

看完浅拷贝之后可能有些小伙伴会有疑问:如果我们只是想修改了 personA 的 subject 对象,但是 personB 的 subject 对象也被修改了,这不就带来了数据安全方面的隐患了吗?所以在某些情况下我们就需要用到深拷贝了。我们还是先看一下深拷贝的含义👇

🥝深拷贝🥝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容。

深拷贝有以下几个特点:

  1. 对于基本数据类型的成员对象,直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。
  2. 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响(和浅拷贝不同)。
  3. 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。
  4. 深拷贝相比于浅拷贝速度较慢并且花销较大。

在这里插入图片描述 下面我们把上面贴出的浅拷贝代码修改一下,模拟一下深拷贝的实现👇

/**
 * 模拟引用对象
 * @description: Subject
 * @author: 庄霸.liziye
 * @create: 2022-03-31 15:43
 **/
public class Subject implements Cloneable{

    private String name;

    public Subject(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Subject{" +
                "name='" + name + '\'' +
                '}';
    }
}
/**
 * 人员类
 * @description: Person
 * @author: 庄霸.liziye
 * @create: 2022-03-31 15:43
 **/
public class Person implements Cloneable {
    /**
     * 引用数据类型
     */
    private Subject subject;

    /**
     * 基础数据类型
     */
    private String name;

    /**
     * 基础数据类型
     */
    private int age;

    public Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 重写clone()方法
     * 如果Subject类中也存在引用对象,则需要和Person类一样去实现深拷贝
     * @return
     */
    @Override
    public Object clone() {
        //深拷贝
        try {
            Person person = (Person) super.clone();
            person.subject = (Subject) subject.clone();
            return person;
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
            return null;
        }
    }

    @Override
    public String toString() {
        return "[Person: " + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
    }
}
/**
 * @description: Test
 * @author: 庄霸.liziye
 * @create: 2022-03-31 15:49
 **/
public class Test {
    public static void main(String[] args) {
        Subject subject = new Subject("张三");
        Person personA = new Person();
        personA.setSubject(subject);
        personA.setName("李四");
        personA.setAge(20);

        Person personB = (Person) personA.clone();
        personB.setName("王五");
        personB.setAge(18);
        Subject subjectB = personB.getSubject();
        subjectB.setName("赵六");
        System.out.println("PersonA:" + personA.toString());
        System.out.println("PersonB:" + personB.toString());
    }
}

在这里插入图片描述 通过运行结果我们可以看出,深拷贝对象后,修改其中某个对象的基础数据类型变量或引用类型的成员变量后都不会对另一个对象造成影响。

平时开发应用软件的过程中,我们可能不经常使用拷贝,但是区分深拷贝和浅拷贝会让我们对 Java 内存结构和运行方式有更深的了解💪。

小结

本人经验有限,有些地方可能讲的没有特别到位,如果您在阅读的时候想到了什么问题,欢迎在评论区留言,我们后续再一一探讨🙇‍

希望各位小伙伴动动自己可爱的小手,来一波点赞+关注 (✿◡‿◡) 让更多小伙伴看到这篇文章~ 蟹蟹呦(●'◡'●)

如果文章中有错误,欢迎大家留言指正;若您有更好、更独到的理解,欢迎您在留言区留下您的宝贵想法。

爱你所爱 行你所行 听从你心 无问东西