基于本文回答
0
评论

Java中的深拷贝和浅拷贝

知识点图片

本文详解Java的浅拷贝与深拷贝。核心区别在于:浅拷贝共享内部引用,修改会互相影响;深拷贝则创建完全独立的副本,彼此隔离。文章还介绍了clone()、构造函数和序列化等实现方式。

核心概念:什么是拷贝?

在Java中,除了基本数据类型(如 int, double, char 等)是按值传递的,对象都是按引用传递的。当你把一个对象变量赋值给另一个时,你只是复制了对象的引用(内存地址),而不是对象本身。

java
Person p1 = new Person("张三");
Person p2 = p1; // 这里没有创建新对象,p2和p1指向同一个内存地址

p2.setName("李四");
System.out.println(p1.getName()); // 输出 "李四"

而“拷贝”或“克隆”的目的是创建一个全新的、独立的对象,其内容与原对象相同。根据拷贝的“深度”,分为浅拷贝和深拷贝。


一个生动的比喻

想象一下,你有一份个人简历(Resume 对象),里面包含了你的基本信息(如姓名,是字符串)和一份工作经历列表(WorkExperience 对象)。

  • 浅拷贝:就像你直接复印了这份简历。复印件上的姓名和你自己写的一样,但是工作经历部分,你只是复印了指向“同一份工作经历文档”的链接。如果你在原件上修改了这份工作经历文档(比如改了公司名),那么通过复印件上的链接找过去,看到的内容也是修改过的。
  • 深拷贝:这相当于你不仅复印了简历的封面,还把简历里提到的那份“工作经历文档”也单独复印了一份,然后把这个新的工作经历文档的复印件附在新的简历复印件上。这样,你修改原件的工作经历,就完全不会影响到复印件里的工作经历。

浅拷贝 (Shallow Copy)

1. 定义

浅拷贝会创建一个新对象,然后将原始对象中的字段值精确地复制到新对象中。

  • 对于基本数据类型(int, double, boolean等):直接复制值。
  • 对于引用类型(String, Array, 自定义对象等):复制的是引用的地址,而不是引用所指向的对象本身。

2. 特点

  • 新对象和原对象是两个独立的对象(内存地址不同)。
  • 但它们内部的引用类型字段指向的是同一个对象
  • 因此,如果通过一个对象修改了其引用的对象内容,另一个对象也会受到影响。

3. 代码示例

我们使用 Object.clone() 方法来说明,这是Java中最常见的实现浅拷贝的方式。

java
// 地址类
class Address implements Cloneable {
    private String city;

    public Address(String city) { this.city = city; }
    public void setCity(String city) { this.city = city; }
    public String getCity() { return city; }

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

// 人员类
class Person implements Cloneable {
    private int age;
    private Address address; // 引用类型

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

    // 省略 getter/setter
    public void setAge(int age) { this.age = age; }
    public void setAddressCity(String city) { this.address.setCity(city); }

    @Override
    public String toString() {
        return "Person [age=" + age + ", address=" + address.getCity() + "]";
    }

    // 实现浅拷贝
    @Override
    public Object clone() throws CloneNotSupportedException {
        // super.clone() 默认是浅拷贝
        return super.clone();
    }
}

public class ShallowCopyTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("北京");
        Person p1 = new Person(30, address);

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

        System.out.println("拷贝前:");
        System.out.println("p1: " + p1); // p1: Person [age=30, address=北京]
        System.out.println("p2: " + p2); // p2: Person [age=30, address=北京]

        // 1. 修改基本数据类型
        p2.setAge(35);

        // 2. 修改引用类型的内容
        p2.setAddressCity("上海");

        System.out.println("\n拷贝后:");
        System.out.println("p1: " + p1); // p1: Person [age=30, address=上海]
        System.out.println("p2: " + p2); // p2: Person [age=35, address=上海]

        // 结论:
        // - 修改p2的基本类型age,p1的age未受影响。
        // - 修改p2的引用类型address的内容,p1的address也跟着变了!因为它们共享同一个Address对象。
    }
}

深拷贝 (Deep Copy)

1. 定义

深拷贝不仅会复制对象本身,还会递归地复制该对象内部引用的所有对象。

2. 特点

  • 新对象和原对象是两个完全独立的对象。
  • 它们内部的引用类型字段指向的也是全新的、独立的对象
  • 修改任何一个对象(包括其引用的对象),都不会影响到另一个。

3. 代码示例

我们修改上面 Person 类的 clone 方法来实现深拷贝。

java
class Person implements Cloneable {
    private int age;
    private Address address;

    // ... 构造函数和 getter/setter 不变 ...

    @Override
    public String toString() {
        return "Person [age=" + age + ", address=" + address.getCity() + "]";
    }
    
    // 实现深拷贝
    @Override
    public Object clone() throws CloneNotSupportedException {
        // 1. 先对Person对象本身进行浅拷贝
        Person clonedPerson = (Person) super.clone();

        // 2. 对内部的引用类型字段,单独进行拷贝
        clonedPerson.address = (Address) this.address.clone(); // 对Address对象也进行拷贝

        return clonedPerson;
    }
}

public class DeepCopyTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("北京");
        Person p1 = new Person(30, address);

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

        System.out.println("拷贝前:");
        System.out.println("p1: " + p1); // p1: Person [age=30, address=北京]
        System.out.println("p2: " + p2); // p2: Person [age=30, address=北京]

        // 1. 修改基本数据类型
        p2.setAge(35);

        // 2. 修改引用类型的内容
        p2.setAddressCity("上海");

        System.out.println("\n拷贝后:");
        System.out.println("p1: " + p1); // p1: Person [age=30, address=北京]
        System.out.println("p2: " + p2); // p2: Person [age=35, address=上海]

        // 结论:
        // - 修改p2的age,p1不受影响。
        // - 修改p2的address内容,p1的address也完全没有改变!因为p2的address是一个全新的对象。
    }
}

实现拷贝的几种方式

  1. 实现 Cloneable 接口并重写 clone() 方法 (如上例)

    • 优点: Java原生支持,对于复杂的对象图,性能可能较高。
    • 缺点: clone() 方法是 protected 的,需要重写并声明为 publicCloneable 是一个标记接口,没有方法,容易忘记实现。代码编写相对复杂,需要逐层处理引用对象的克隆。
  2. 使用拷贝构造函数 (Copy Constructor)

    • 优点: 简单、直观、类型安全,可以自由控制拷贝逻辑。
    • 缺点: 需要为每个类编写一个拷贝构造函数。
    java
    class Person {
        private int age;
        private Address address;
    
        // 拷贝构造函数
        public Person(Person original) {
            this.age = original.age;
            // 深拷贝 Address
            this.address = new Address(original.address.getCity()); 
        }
        // ... 其他构造函数 ...
    }
    // 使用: Person p2 = new Person(p1);
  3. 使用序列化 (Serialization)

    • 将对象写入字节流,再从字节流中读回来,这个过程会创建一个全新的对象图。这是实现真正深拷贝的最简单、最彻底的方法。
    • 优点: 实现简单,能处理复杂的对象图(包括循环引用)。
    • 缺点: 性能开销较大。所有涉及的类都必须实现 java.io.Serializable 接口。
    java
    public static <T extends Serializable> T deepCopy(T obj) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        oos.close();
    
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        T copy = (T) ois.readObject();
        ois.close();
        
        return copy;
    }
    // 使用: Person p2 = deepCopy(p1);

总结对比

特性 浅拷贝 (Shallow Copy) 深拷贝 (Deep Copy)
定义 只复制对象本身和基本类型字段的值 递归复制对象及其引用的所有对象
引用类型字段 复制引用地址,新旧对象共享 创建并复制新的对象,新旧对象完全独立
独立性 不完全独立,修改引用对象会相互影响 完全独立,互不影响
实现复杂度 简单,Object.clone() 默认就是 相对复杂,需要手动处理每个引用类型
适用场景 对象内引用不可变(如String)或希望共享状态时 需要一个对象的完整独立快照,避免副作用

如何选择?

  • 当你确定对象中的所有引用类型都是不可变(immutable)的(例如 String, Integer 等),那么浅拷贝和深拷贝的效果是一样的,使用更简单的浅拷贝即可。
  • 当你希望创建一个对象的副本,并且不希望副本的任何改动影响到原始对象时,必须使用深拷贝。
  • 当性能是首要考虑因素,且可以接受共享某些内部对象时,浅拷贝是更好的选择。
右滑查看面试常问