Java的深拷贝和浅拷贝

Java的深拷贝和浅拷贝

对象拷贝

在展开说深拷贝和浅拷贝之前,先来阐述阐述一下什么是对象拷贝。对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去。

可以简单类比为在电脑上复制文件,这时候,复制普通文件和复制链接就产生了差异,这个差异就是接下来需要分析的深拷贝和浅拷贝的差异。

对象拷贝的实现

在Java中如果想要实现拷贝(忽略对象之间使用=号),只能使用clone方法。clone方法使用protect修饰,声明在Object上,也就是所有Object子对象都可以使用clone方法进行对象拷贝。

1
protected native Object clone() throws CloneNotSupportedException;

在注释中可以看到,如果这个类没有继承自Cloneable接口,那么它会抛出CloneNotSupportedException 异常。

在实现接口,并调用clone时,就能完成对象的拷贝。

深拷贝和浅拷贝

在对象拷贝章节类比电脑上复制文件一样,针对普通文件和链接文件有不同的处理方式,这种处理方式在Java对象拷贝的上的体现就是深拷贝。

在 Java 中,除了基本数据类型(元类型)之外,还存在 类的实例对象 这个引用数据类型。如果再拷贝对象的过程中,只对基本类型的变量进行了值得复制,却对引用类型只做了引用的复制(也就是内存地址引用),没有真正复制引用到的对象。此时的对象拷贝就叫做浅拷贝

与之相反,不光对基本数据类型执行了值得复制,而且在复制引用类型复制时,不是仅仅传递引用,而是将引用到的对象真正的复制(分配内存),此时的对象拷贝就叫做深拷贝

深拷贝和浅拷贝的区别

其实上面在引申概念时,就已经得出深拷贝和浅拷贝的区别了,深拷贝不光要拷贝进本数据类型的值,还要完成对引用类型创建一个新的对象,并复制其内的成员变量。

深拷贝和浅拷贝的实例

  • 浅拷贝用例
    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
    public class CloneTest implements Cloneable {
    public int x;
    public SonClone son;

    public int getX() {
    return x;
    }

    public void setX(int x) {
    this.x = x;
    }

    public SonClone getSon() {
    return son;
    }

    public void setSon(SonClone son) {
    this.son = son;
    }

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

    @Test
    public void testClone(){
    CloneTest test = new CloneTest();
    test.setX(127);
    test.setSon(new SonClone());
    try {
    CloneTest clone = (CloneTest) test.clone();
    // 比较test和复制对象copy
    System.out.println("test == clone --> "
    +(test == clone));
    System.out.println("test.hash == clone.hash --> "
    +(test.hashCode() == clone.hashCode()));
    System.out.println("test.getClass() == clone.getClass() --> "
    + (test.getClass() == clone.getClass()));
    System.out.println("test.son == clone.son --> "
    +(test.getSon() == clone.getSon()));
    System.out.println("test.son.hash == clone.son.hash --> "
    +(test.getSon().hashCode() == clone.getSon().hashCode()));
    } catch (CloneNotSupportedException e) {
    e.printStackTrace();
    }
    }
    class SonClone implements Cloneable{
    int a;
    }

    }

浅拷贝的执行结果如下:

1
2
3
4
5
test == clone --> false
test.hash == clone.hash --> false
test.getClass() == clone.getClass() --> true
test.son == clone.son --> true
test.son.hash == clone.son.hash --> true

可以看到,使用clone可以复制对象,对象的hashcode已经不相同了,但是引用对象却没有执行复制对象的过程,返回的hashcode值仍然是相同的,也就是仅仅复制了引用。

  • 深拷贝用例
    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
    public class DeepClone implements Cloneable{ public int x; public SonClone son;
    public int getX() {
    return x;
    }

    public void setX(int x) {
    this.x = x;
    }

    public SonClone getSon() {
    return son;
    }

    public void setSon(SonClone son) {
    this.son = son;
    }

    class SonClone implements Cloneable{
    int name;

    public int getName() {
    return name;
    }

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

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
    DeepClone clone = (DeepClone) super.clone();
    clone.son = (SonClone) this.son.clone();
    return clone;
    }

    @Test
    public void testClone(){
    DeepClone test = new DeepClone();
    test.setX(127);
    test.setSon(new SonClone());
    try {
    DeepClone clone = (DeepClone) test.clone();
    // 比较test和复制对象copy
    System.out.println("test == clone --> "
    +(test == clone));
    System.out.println("test.hash == clone.hash --> "
    +(test.hashCode() == clone.hashCode()));

    System.out.println("test.getClass() == clone.getClass() --> "
    + (test.getClass() == clone.getClass()));
    System.out.println("test.x == clone.x --> "
    +(test.getX() == clone.getX()));
    System.out.println("test.son == clone.son --> "
    +(test.getSon() == clone.getSon()));
    System.out.println("test.son.hash == clone.son.hash --> "
    +(test.getSon().hashCode() == clone.getSon().hashCode()));
    } catch (CloneNotSupportedException e) {
    e.printStackTrace();
    }
    }
    }

这里深度拷贝可以看出在父类使用clone时,会手动将clone出的父类中的引用指向复制clone出来的子类对象。这时对父类执行了深拷贝,但实则对子类进行了一次浅拷贝。结果显而易见,最后的引用类型值和hashcode都不相同。

总结

拷贝对象需要使用clone方法,并需要继承Cloneable接口,如果不手动重写clone方法,则默认会只能执行浅拷贝。

浅拷贝只会复制基本数据类型的值,而不会复制引用类型的对象,而深拷贝需要手动编写clone方法来达到既能复制基本数据值,又能够完成对引用类型的对象的复制。

-------------本文结束感谢您的阅读-------------