Bootstrap

Java中的数据复制——详解浅拷贝与深拷贝

        在 Java 中,浅拷贝(Shallow Copy)和深拷贝(Deep Copy)是两个重要概念,尤其在操作对象或数据结构时,它们决定了如何复制数据,以及数据之间是否存在关联。下面我们来详细解释它们:

1. 浅拷贝(Shallow Copy)

        浅拷贝在 Java 中意味着复制一个对象时,仅复制该对象的基本字段,但如果对象内部有引用类型字段(例如数组、集合、对象),那么这些引用类型字段不会被复制,它们只是被共享。换句话说,浅拷贝只是复制对象的引用,而不是引用指向的实际对象。

        在 Java 中,浅拷贝通常通过 Object 类中的 clone() 方法 实现。默认的 clone() 方法会进行浅拷贝。

浅拷贝实现:

        Java 中的 clone() 方法要求类实现 Cloneable 接口,并覆盖 clone() 方法。

class Person implements Cloneable {
    String name;
    int age;
    Address address;  // 引用类型

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

    // 覆盖 clone() 方法实现浅拷贝
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();  // 浅拷贝
    }
}

class Address {
    String city;

    public Address(String city) {
        this.city = city;
    }
}

浅拷贝效果:

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("New York");
        Person p1 = new Person("Alice", 30, address);

        Person p2 = (Person) p1.clone();  // 浅拷贝

        // 修改 p2 的地址
        p2.address.city = "Los Angeles";

        // p1 的地址也会改变
        System.out.println(p1.address.city);  // 输出 "Los Angeles"
    }
}

        在这个例子中,虽然我们浅拷贝了 Person 对象,但它的 address 引用仍然指向同一个对象,因此修改 p2addressp1 也会随之改变。

浅拷贝的特点

  • 基本数据类型:值被直接复制(例如 intboolean 等)。
  • 引用类型:复制的是引用,多个对象共享同一个内存地址。

2. 深拷贝(Deep Copy)

        深拷贝在 Java 中意味着不仅要复制对象的基本字段,还要递归地复制所有引用类型的字段。这意味着深拷贝将创建完全独立的副本,即使内部嵌套了复杂的对象结构,原对象和副本之间也不会共享引用。

        在 Java 中,深拷贝的实现比较复杂,因为默认的 clone() 方法只进行浅拷贝,因此需要手动覆盖 clone() 方法,确保对引用类型进行递归拷贝。

深拷贝实现:

class Person implements Cloneable {
    String name;
    int age;
    Address address;  // 引用类型

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

    // 覆盖 clone() 方法实现深拷贝
    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 首先浅拷贝基本字段
        Person clonedPerson = (Person) super.clone();

        // 递归拷贝引用类型的字段
        clonedPerson.address = (Address) this.address.clone();

        return clonedPerson;  // 返回完全独立的副本
    }
}

class Address implements Cloneable {
    String city;

    public Address(String city) {
        this.city = city;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();  // Address 的浅拷贝
    }
}

深拷贝效果:

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("New York");
        Person p1 = new Person("Alice", 30, address);

        Person p2 = (Person) p1.clone();  // 深拷贝

        // 修改 p2 的地址
        p2.address.city = "Los Angeles";

        // p1 的地址不会改变
        System.out.println(p1.address.city);  // 输出 "New York"
    }
}

        在这个例子中,p2address 是独立的对象,所以修改 p2 的地址不会影响 p1 的地址。

深拷贝的特点

  • 所有字段(包括引用类型)都会被独立复制。
  • 原对象与副本完全独立,任何一方的修改都不会影响另一方。

3. 浅拷贝与深拷贝的比较

对比点浅拷贝深拷贝
复制对象层次只复制第一层对象,引用类型字段共享同一对象递归复制所有对象及其引用类型字段
引用共享内部引用类型共享同一内存地址内部对象完全独立,不共享任何引用
适用场景适用于简单对象,没有深层次引用适用于复杂对象结构,深层对象独立

4. 深拷贝的其他实现方式

        除了使用 clone() 方法,还可以使用其他方式实现深拷贝,比如:

  • 序列化与反序列化:通过将对象序列化成字节流,再反序列化为新对象。这个方法可以保证深拷贝,但要求对象实现 Serializable 接口。
  • 手动拷贝构造方法:编写一个拷贝构造函数来逐个字段地进行深拷贝。

序列化深拷贝示例:

import java.io.*;

class Person implements Serializable {
    String name;
    int age;
    Address address;

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

    // 序列化实现深拷贝
    public Person deepCopy() throws IOException, ClassNotFoundException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(this);

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        return (Person) ois.readObject();
    }
}

        这种方法虽然可以实现深拷贝,但效率较低,不适用于高性能要求的场景。

5.总结

  • 浅拷贝 复制对象的第一层,引用类型字段共享同一对象。
  • 深拷贝 递归复制对象及其所有引用,确保新对象与原对象完全独立。
;