java,深拷贝和浅拷贝

在 Java 中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是对象拷贝的两种方式,主要区别在于它们如何处理对象的内部引用。

目录

一、浅拷贝(Shallow Copy)

实现方式

二、深拷贝(Deep Copy)

实现方式

1、手动深拷贝

2、通过序列化实现深拷贝

深拷贝中的注意事项

深拷贝的应用场景

总结


一、浅拷贝(Shallow Copy)

浅拷贝是指仅拷贝对象的基本类型字段引用类型字段的引用,而不是引用类型所指向的对象本身。因此,浅拷贝后的对象与原对象的引用类型字段共享相同的对象。如果原对象或拷贝对象中的引用类型被修改,两个对象都会受到影响。

实现方式

通过 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;
    }

    @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 person1 = new Person("John", 25, address);
        Person person2 = (Person) person1.clone();  // 浅拷贝
        System.out.println(person1.address.city);  // 输出 "New York"
        System.out.println(person2.address.city);  // 输出 "New York"

        person2.address.city = "Los Angeles";
        System.out.println(person1.address.city);  // 输出 "Los Angeles",由于是浅拷贝,两者共享相同的地址引用
    }
}

在这个例子中,person1person2 共享同一个 Address 对象,修改 person2address 会影响 person1

二、深拷贝(Deep Copy)

深拷贝不仅拷贝对象的基本类型字段,还会递归地拷贝对象中所有的引用对象。因此,拷贝后的对象与原对象完全独立,互不影响。修改一个对象的引用类型字段不会影响另一个对象。

 深拷贝之所以能够实现对象的完全独立,关键在于它对对象中的引用类型字段进行了逐层递归的复制。其基本原理如下:

  • 基本数据类型的复制:对于 Java 中的基本数据类型(如 intdoublechar 等),深拷贝与浅拷贝是一样的,即直接拷贝数值。这是因为基本类型本质上是存储在栈中的固定长度的值,不涉及引用或指针。

  • 引用类型的处理:深拷贝时,引用类型(如对象、数组等)不会直接拷贝引用,而是会创建一个全新的副本,并且这个副本与原引用指向的对象互不相关。这种“递归”拷贝意味着如果对象内部还有对象,它们也会被逐一深拷贝。

实现方式

  • 手动实现:手动编写深拷贝逻辑,通过递归地调用 clone() 方法来拷贝所有引用对象。
  • 通过序列化实现:将对象序列化为字节流,再反序列化为新对象。

特点:

  • 基本类型字段会被完全复制。
  • 引用类型字段也会被完全复制,创建新的对象。
  • 深拷贝相比浅拷贝更耗时,因为需要递归复制所有引用类型。

1、手动深拷贝

手动实现深拷贝时,程序员需要遍历每个引用类型的字段,并递归调用 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;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person cloned = (Person) super.clone();
        cloned.address = (Address) this.address.clone();  // 深拷贝 address
        return cloned;
    }
}

class Address implements Cloneable {
    String city;

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

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

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("New York");
        Person person1 = new Person("John", 25, address);
        Person person2 = (Person) person1.clone();  // 深拷贝

        System.out.println(person1.address.city);  // 输出 "New York"
        System.out.println(person2.address.city);  // 输出 "New York"

        person2.address.city = "Los Angeles";
        System.out.println(person1.address.city);  // 仍输出 "New York",因为进行了深拷贝,两个对象完全独立
    }
}

在这个例子中,Person 类中引用了 Address 对象。为了确保深拷贝,我们在 Person 类的 clone() 方法中显式地对 address 字段调用 clone() 方法,从而实现 Address 的深拷贝。这样 Person 对象和 Address 对象都是独立的。 

2、通过序列化实现深拷贝

如果对象和所有子对象实现了 Serializable 接口,可以使用序列化来实现深拷贝。

序列化是深拷贝的一种简便方式。通过将对象序列化为字节流,然后再将字节流反序列化为新的对象,可以实现深拷贝。这种方式适用于复杂的对象图,甚至对象之间有循环引用时也能正确处理。

通过序列化实现深拷贝的关键步骤如下:

  • 序列化:将对象及其所有引用对象通过字节流写入到一个存储介质中(例如 ByteArrayOutputStream)。
  • 反序列化:从存储介质中读取字节流,并重新构建对象及其引用对象,从而生成与原始对象完全独立的新对象。
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 byteOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteOut);
        out.writeObject(this);

        // 反序列化
        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
        ObjectInputStream in = new ObjectInputStream(byteIn);
        return (Person) in.readObject();
    }
}

class Address implements Serializable {
    String city;

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

public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Address address = new Address("New York");
        Person person1 = new Person("John", 25, address);
        Person person2 = person1.deepCopy();  // 深拷贝

        person2.address.city = "Los Angeles";
        System.out.println(person1.address.city);  // 输出 "New York",两个对象独立
    }
}

 在这个例子中,Person 对象和其引用的 Address 对象都实现了 Serializable 接口。通过序列化和反序列化,生成了完全独立的 PersonAddress 对象。

深拷贝中的注意事项

(1)对象之间的引用关系

深拷贝必须递归地处理对象中的所有引用类型,因此可能会遇到复杂的对象图结构。如果对象之间存在循环引用,序列化方案可以很好地处理这种情况,因为序列化机制会自动检测和处理对象循环引用的问题。

(2)性能成本

深拷贝的性能开销通常比浅拷贝大,尤其是当对象结构复杂时,递归地创建新的对象会消耗更多的时间和内存。

序列化虽然实现较为简便,但由于涉及字节流的创建和解析,性能相对较低。

(3)不可变对象

对于不可变对象(如 StringInteger 等),深拷贝和浅拷贝都无需特别处理,因为不可变对象在拷贝中不会受到修改的影响。

深拷贝的应用场景

(1)避免副作用:在需要确保对象之间完全独立,防止一个对象的修改影响另一个对象时,深拷贝是必需的。例如,当在多线程环境中使用对象时,使用深拷贝可以避免共享对象带来的数据不一致问题。

(2)复杂对象结构:当对象中嵌套了其他对象,并且这些对象可能会被修改时(例如树结构、图结构),深拷贝确保了各个层次的对象都能保持独立。

总结

  • 浅拷贝:拷贝对象的基本类型字段和引用类型的引用,两个对象共享相同的引用对象。
  • 深拷贝:完全独立的对象拷贝,包括对象的引用类型字段的递归拷贝。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值