当前位置:   article > 正文

基础面试题:深拷贝和浅拷贝详解以及实例_java深拷贝和浅拷贝面试题

java深拷贝和浅拷贝面试题


csdn-start

前言:

深拷贝和浅拷贝是在编程中经常遇到的概念,它们在数据处理和对象复制中起着重要的作用。在本文中,我们将对深拷贝和浅拷贝进行比较,并提供一些示例来帮助读者更好地理解这两个概念。


浅拷贝

概念

复制基本类型的属性;引用类型的属性复制,复制栈中的变量 和 变量指向堆内存中的对象的指针,不复制堆内存中的对象。

如图

image-20231117185244518

特点

​ 1.对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。
2.对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。

实现

​ 实现 Cloneable 接口,重写 clone() 方法

/**
 * @ClassName ShallowCopyDemo
 * @Description: 实现 Cloneable 接口,重写 clone() 方法。
 * @Author fking
 * @Date 2020/4/30 0030
 * @Version V1.0
 **/
public class ShallowCopyDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person p1 = new Person(1, "fking01");//创建对象 Person p1
        Person p2 = (Person)p1.clone();//克隆对象 p1
        p2.setName("fking02");//修改 p2的name属性,p1的name未变
        System.out.println(p1);
        System.out.println(p2);

        p2 = p1;
        p2.setName("fking02");//修改 p2的name属性,p1的也跟着变
        System.out.println(p1);
        System.out.println(p2);
    }
}

class Person implements Cloneable {

    private int pid;

    private String name;

    public Person(int pid, String name) {
        this.pid = pid;
        this.name = name;
        System.out.println("Person constructor call");
    }

    public int getPid() {
        return pid;
    }

    public void setPid(int pid) {
        this.pid = pid;
    }

    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 "Person [pid:" + pid + ", name:" + name + "]";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

深拷贝

概念

​ 复制基本类型的属性;引用类型的属性复制,复制栈中的变量 和 变量指向堆内存中的对象的指针和堆内存中的对象。

img

特点

​ 1.对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。

​ 2.对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。

​ 3.对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。

​ 4.深拷贝相比于浅拷贝速度较慢并且花销较大。

实现方式

​ 1.对象的属性的Class 也实现 Cloneable 接口,在克隆对象时也手动克隆属性

/**
 * @ClassName DeepCopyDemo01
 * @Description: 对象的属性的Class 也实现 Cloneable 接口,在克隆对象时也手动克隆属性。
 * @Author fking
 * @Date 2020/4/30 0030
 * @Version V1.0
 **/
public class DeepCopyDemo01 {
    public static void main(String[] args) throws CloneNotSupportedException {
        DPerson01 p1 = new DPerson01(1, "fking01", new DFood01("food01"));//创建Person 对象 p1
        DPerson01 p2 = (DPerson01)p1.clone();//克隆p1
        p2.setName("fking02");//修改p2的name属性
        p2.getFood().setName("food02");//修改p2的自定义引用类型 food 属性
        System.out.println(p1);//修改p2的自定义引用类型 food 属性被改变,p1的自定义引用类型 food 属性也随之改变,说明p2的food属性,只拷贝了引用,没有拷贝food对象
        System.out.println(p2);
    }
}

class DPerson01 implements Cloneable {

    private int pid;

    private String name;

    private DFood01 food;

    public DPerson01(int pid, String name, DFood01 food) {
        this.pid = pid;
        this.name = name;
        this.food = food;
        System.out.println("DPerson01 constructor call");
    }

    public int getPid() {
        return pid;
    }

    public void setPid(int pid) {
        this.pid = pid;
    }

    public String getName() {
        return name;
    }

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

    /**
     * 对象的属性的Class 也实现 Cloneable 接口,在克隆对象时也手动克隆属性。
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        DPerson01 p = (DPerson01)super.clone();
        p.setFood((DFood01)p.getFood().clone());
        return p;
    }

    @Override
    public String toString() {
        return "DPerson01 [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
    }

    public DFood01 getFood() {
        return food;
    }

    public void setFood(DFood01 food) {
        this.food = food;
    }

}

class DFood01 implements Cloneable {

    private String name;

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

    public String getName() {
        return name;
    }

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

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

​ 2.结合序列化(JDK java.io.Serializable 接口、JSON格式、XML格式等),完成深拷贝

/**
 * @ClassName DeepCopyDemo02
 * @Description: 结合序列化(JDK java.io.Serializable 接口、JSON格式、XML格式等),完成深拷贝
 * @Author fking
 * @Date 2020/4/30 0030
 * @Version V1.0
 **/
public class DeepCopyDemo02 {
    public static void main(String[] args) throws CloneNotSupportedException {
        DPerson02 p1 = new DPerson02(1, "fking01", new SFood02("food01"));//创建 DPerson02 对象 p1
        DPerson02 p2 = (DPerson02)p1.cloneBySerializable();//克隆 p1
        p2.setName("fking02");//修改 p2 的 name 属性
        p2.getFood().setName("food02");//修改 p2 的自定义引用类型 food 属性
        System.out.println(p1);//修改 p2 的自定义引用类型 food 属性被改变,p1的自定义引用类型 food 属性未随之改变,说明p2的food属性,只拷贝了引用和 food 对象
        System.out.println(p2);
    }
}

class DPerson02 implements Cloneable, Serializable {

    private static final long serialVersionUID = -7710144514831611031L;

    private int pid;

    private String name;

    private SFood02 food;

    public DPerson02(int pid, String name, SFood02 food) {
        this.pid = pid;
        this.name = name;
        this.food = food;
        System.out.println("DPerson02 constructor call");
    }

    public int getPid() {
        return pid;
    }

    public void setPid(int pid) {
        this.pid = pid;
    }

    public String getName() {
        return name;
    }

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

    /**
     * 通过序列化完成克隆
     * @return
     */
    public Object cloneBySerializable() {
        Object obj = null;
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            obj = ois.readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return obj;
    }

    @Override
    public String toString() {
        return "DPerson02 [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
    }

    public SFood02 getFood() {
        return food;
    }

    public void setFood(SFood02 food) {
        this.food = food;
    }

}

class SFood02 implements Serializable {

    private static final long serialVersionUID = -3443815804346831432L;

    private String name;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105

使用场景

浅拷贝的使用场景

当对象较大,但只需复制引用类型时,可以使用浅拷贝来节省内存和提高效率。此外,当你需要修改原对象的属性值时,浅拷贝也可以起到作用。

深拷贝的使用场景

当你需要复制的对象包含循环引用或复杂的数据结构时,或者你需要完全隔离原对象和新对象以避免相互影响时,就需要使用深拷贝。


注意

数据结构的嵌套

深拷贝和浅拷贝的区别在于是否递归地复制嵌套的数据结构。如果数据结构中包含了其他的数据结构,比如嵌套的列表或字典,那么在进行拷贝时需要注意是否需要递归地进行深拷贝。

对象的引用

浅拷贝只是复制了对象的引用,而不是对象本身。这意味着如果原始对象发生了改变,拷贝对象也会受到影响。因此,在使用浅拷贝时需要注意对原始对象的修改是否会对拷贝对象产生影响。

性能和内存消耗

深拷贝通常需要更多的时间和内存资源,因为它需要递归地复制整个数据结构。在处理大型数据结构时,深拷贝可能会导致性能问题和内存消耗过高的情况。因此,在选择拷贝方式时需要权衡性能和内存消耗。

可变对象和不可变对象

对于不可变对象,浅拷贝和深拷贝没有太大的区别,因为不可变对象无法被修改。但对于可变对象,浅拷贝和深拷贝的区别就非常重要了。在使用可变对象时,需要根据具体的需求选择合适的拷贝方式。


总结

通过本文的介绍,我们希望读者能够清楚地理解深拷贝和浅拷贝的区别以及它们在编程中的应用。深拷贝和浅拷贝在不同的场景下有不同的用途,正确地选择和使用拷贝方式可以提高代码的效率和可靠性。


写在最后

感谢您的支持和鼓励!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/68446?site
推荐阅读
相关标签