深克隆和浅克隆的区别?

参考回答

浅克隆深克隆是 Java 对象复制的两种方式,主要区别在于是否复制对象的引用类型字段

  1. 浅克隆
    • 只复制对象的基本数据类型和引用类型的引用地址,不会复制引用类型所指向的对象。
    • 被克隆对象与原对象的引用字段指向同一块内存。
    • 通过实现 Cloneable 接口并重写 clone() 方法实现。
  2. 深克隆
    • 复制对象的基本数据类型,并递归地复制引用类型所指向的对象(即引用字段所指的对象也会被克隆)。
    • 被克隆对象与原对象完全独立,互不影响。
    • 通常通过序列化与反序列化、或递归调用 clone() 方法实现。

详细讲解与拓展

1. 浅克隆的原理与示例

浅克隆会将对象的基本数据类型字段复制一份,但对于引用类型字段,只会复制它们的引用地址,而不会复制引用指向的对象。

示例

class Address implements Cloneable {
    String city;

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // 默认浅克隆
    }
}

class Person implements Cloneable {
    String name;
    Address address;

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // 默认浅克隆
    }
}

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

        // 浅克隆
        Person person2 = (Person) person1.clone();

        System.out.println("Before modification:");
        System.out.println("person1 address: " + person1.address.city);
        System.out.println("person2 address: " + person2.address.city);

        // 修改克隆对象的引用字段
        person2.address.city = "Los Angeles";

        System.out.println("\nAfter modification:");
        System.out.println("person1 address: " + person1.address.city);
        System.out.println("person2 address: " + person2.address.city);
    }
}

输出

Before modification:
person1 address: New York
person2 address: New York

After modification:
person1 address: Los Angeles
person2 address: Los Angeles

解释

  • 浅克隆时,person2.addressperson1.address 指向同一个对象。
  • 修改 person2.address.city 会影响到 person1.address.city

2. 深克隆的原理与示例

深克隆会递归地复制对象的所有字段,包括引用类型字段所指向的对象,使克隆对象和原对象完全独立。

方法 1:通过递归调用 clone() 实现深克隆

class Address implements Cloneable {
    String city;

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // 默认浅克隆
    }
}

class Person implements Cloneable {
    String name;
    Address address;

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 深克隆
        Person cloned = (Person) super.clone();
        cloned.address = (Address) address.clone(); // 克隆引用字段
        return cloned;
    }
}

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

        // 深克隆
        Person person2 = (Person) person1.clone();

        System.out.println("Before modification:");
        System.out.println("person1 address: " + person1.address.city);
        System.out.println("person2 address: " + person2.address.city);

        // 修改克隆对象的引用字段
        person2.address.city = "Los Angeles";

        System.out.println("\nAfter modification:");
        System.out.println("person1 address: " + person1.address.city);
        System.out.println("person2 address: " + person2.address.city);
    }
}

输出

Before modification:
person1 address: New York
person2 address: New York

After modification:
person1 address: New York
person2 address: Los Angeles

解释

  • 深克隆时,person2.address 是一个新的对象,与 person1.address 相互独立。
  • 修改 person2.address.city 不会影响 person1.address.city

方法 2:通过序列化实现深克隆

使用序列化(Serializable 接口)和反序列化,自动实现深克隆。

import java.io.*;

class Address implements Serializable {
    String city;

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

class Person implements Serializable {
    String name;
    Address address;

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

public class DeepCloneWithSerialization {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Address address = new Address("New York");
        Person person1 = new Person("Alice", address);

        // 序列化与反序列化实现深克隆
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(person1);

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

        System.out.println("Before modification:");
        System.out.println("person1 address: " + person1.address.city);
        System.out.println("person2 address: " + person2.address.city);

        // 修改克隆对象的引用字段
        person2.address.city = "Los Angeles";

        System.out.println("\nAfter modification:");
        System.out.println("person1 address: " + person1.address.city);
        System.out.println("person2 address: " + person2.address.city);
    }
}

输出

Before modification:
person1 address: New York
person2 address: New York

After modification:
person1 address: New York
person2 address: Los Angeles

解释

  • 序列化和反序列化的过程会创建一个完全独立的对象,自动实现深克隆。

3. 浅克隆与深克隆的区别总结

特性 浅克隆 深克隆
复制内容 只复制基本数据类型和引用类型的地址 递归地复制所有字段,包括引用类型所指的对象
引用类型字段 原对象与克隆对象共享引用类型字段 原对象与克隆对象的引用字段互相独立
实现复杂度 简单,通过 super.clone() 实现 较复杂,需要递归 clone() 或序列化
使用场景 对象结构简单,不涉及深层嵌套引用的情况 对象结构复杂,包含深层嵌套引用的情况

4. 拓展知识

  1. Cloneable 接口的限制
    • Cloneable 接口只是一个标记接口,没有定义方法。
    • 如果不重写 clone() 方法,会抛出 CloneNotSupportedException
  2. 替代方案
    • 在大多数实际开发中,Cloneable 的使用较少,因为其不够灵活且易出错。
    • 常用的替代方案是:
      • 构造函数:通过手动编写复制构造函数创建新对象。
      • 序列化:用序列化和反序列化实现深克隆。
  3. 性能对比
    • 浅克隆的性能较好,因为只需复制字段引用。
    • 深克隆的性能相对较低,尤其是通过序列化实现时,需要大量 I/O 操作。

5. 总结

  • 浅克隆:
    • 简单快捷,但无法复制引用类型指向的对象。
    • 使用场景:对象结构简单或对引用字段的共享没有影响。
  • 深克隆:
    • 完全独立的复制,适用于复杂嵌套对象。
    • 实现方式:递归调用 clone() 方法或使用序列化。

发表回复

后才能评论