likes
comments
collection
share

Java 基础知识之 深拷贝与浅拷贝(Cloneable接口、clone方法)

作者站长头像
站长
· 阅读数 24

概述

浅拷贝(Shallow Copy)和深拷贝(DeepCopy)是在复制对象或数据结构(ArrayListHashMap...)时的两种不同拷贝方式,它们的主要区别在于拷贝的程度。

Java 的类型有基本数据类型和引用类型,基本数据类型是可以由 CPU 直接操作的类型,无论是深拷贝还是浅拷贝,都是会复制出另一份。而引用类型仅仅是一个指针,指向的是这个对象在堆内存中分配的内存。

在浅拷贝时,仅仅是将这个指针拷贝了一份出来,两个指针都指向相同的堆内存地址;而深拷贝时,拷贝的就不仅仅是一个指针,还会在堆内存中将原来的对象也拷贝出来一份。 Java 基础知识之 深拷贝与浅拷贝(Cloneable接口、clone方法)

我们将在例子中讲解这个方法和接口的使用。

浅拷贝(Shallow Copy)

浅拷贝创建一个新的对象,但是只复制原始对象的基本数据类型的字段或引用(地址),而不复制引用指向的对象。这意味着新对象和原始对象中的引用指向相同的对象,这样的话,对新对象所做的修改可能会影响到原始对象,因为它们引用了相同的堆内存。

例如下面的类,Person 类包含一个基本数据类型 int age,和一个引用数据类型 String name

class Person implements Cloneable {
    
    String name;
    int age;

    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }

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

这个类使用了 Object 的默认 clone 方法,也就是浅拷贝。在调用这个类的 clone 方法时,仅仅会浅拷贝出一个新的 Person 对象,且这种拷贝方法不会调用构造方法。

我们通过如下代码进行拷贝操作:

try {
    Person person1 = new Person("AAA", 30);
    Person person2 = (Person) person1.clone();    //拷贝操作

    System.out.println("person1 = " + person1 + ", person1.name = String@" + Integer.toHexString(System.identityHashCode(person1.name)));
    System.out.println("person2 = " + person2 + ", person2.name = String@" + Integer.toHexString(System.identityHashCode(person2.name)));
} catch (CloneNotSupportedException exception) {
    exception.printStackTrace();
}

person2 是通过 person1 拷贝出来的对象,由于默认使用了 Object.clone 的浅拷贝,person2 拷贝了 person1age,但由于 name 是引用类型,person2 只是拷贝了 person1name 的指针而言。看一下输出:

mi@mi-HP:~/develop/code/JavaCode$ java Hello.java
person1 = Person@291caca8, person1.name = String@385e9564
person2 = Person@445b295b, person2.name = String@385e9564

可以看到,两个 Person 对象是不同的,但是它们引用的 name 是相同的。这就是浅拷贝。

深拷贝(Deep Copy)

深拷贝创建一个新的对象,并且递归地复制原始对象的所有字段和引用指向的对象,而不仅仅是复制引用本身。

深拷贝会递归复制整个对象结构,包括对象内部的对象,确保新对象和原始对象之间的所有关系都是独立的,这也意味着对新对象所做的修改不会影响到原始对象,因为它们拥有彼此独立的副本。

在编码时,让对象支持深拷贝需要对其依赖的每一个引用类型添加额外的处理,例如上面的浅拷贝的例子,我们将其更改为深拷贝:

class Person implements Cloneable {
    
    String name;
    int age;

    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person person = (Person) super.clone();
        person.name = new String(this.name);
        return person;
    }
}

此处只有 clone 方法的更改,深拷贝需要对类中的每一个引用类型做处理,这里就是对 name 这个引用类型做了处理,新创建了一个对象。如果这个类中还包含其他的引用类型,就还需要依次处理,例如调用其 clone 方法或是其拷贝构造方法。

以下是深拷贝的输出结果:

mi@mi-HP:~/develop/code/JavaCode$ java Hello.java
person1 = Person@5c671d7f, person1.name = String@757277dc
person2 = Person@77f80c04, person2.name = String@1dac5ef

可以看到,不仅 person 是不一样的地址,连 person.name 也是不一样的对象。 Java 基础知识之 深拷贝与浅拷贝(Cloneable接口、clone方法)

Cloneable 接口 和 clone 方法

在上面两个例子中,拷贝是通过覆写 Object.clone 方法来完成的,但如果覆写这个方法,就需要类能够实现 Cloneable 接口,否则在调用 clone 时会出现 CloneNotSupportedException 异常。不过接口仅仅是做个标记,标记这个类是支持 clone 方法的。

无论是写深拷贝还是写浅拷贝,基本都是通过 clone 方法来进行的。因此记住以下几点你就会写 clone 方法了:

  • 添加 Cloneable 接口
  • 覆写 clone 方法
  • 深拷贝需要处理类中的引用类型的拷贝
  • Objcet.clone 默认是浅拷贝

不过回过头想一下,这种设计好么,我记得接口不是这么用的,你 clone 方法放到 Cloneable 接口里面是不是更合理一些。确实也有很多人说这个 Cloneable 是个比较糟糕的设计,估计也是受限于早期 Java 的不完善,只能各种折中,最终整出来折中设计了。

转载自:https://juejin.cn/post/7386490986434428939
评论
请登录