当前位置: 首页 > news >正文

深拷贝与浅拷贝

在 Java 中,对象拷贝是常见的操作,主要分为浅拷贝和深拷贝两种方式,下面为你详细介绍它们的概念、实现方式以及区别。

1. 浅拷贝(Shallow Copy)

1.1 概念

浅拷贝创建一个新对象,新对象的属性值会复制原对象的属性值。对于基本数据类型,会直接复制其值;而对于引用数据类型,只是复制引用,即新对象和原对象的引用数据类型属性指向同一个内存地址。这意味着如果修改新对象中引用数据类型属性的内容,原对象中对应的属性内容也会被修改。

1.2 实现方式

在 Java 中,要实现浅拷贝,可以让类实现 Cloneable 接口并重写 clone() 方法。Cloneable 接口是一个标记接口,它本身不包含任何方法,只是告诉 Java 虚拟机该类可以被克隆。

示例代码

class Address {
    String street;
    String city;

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

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();
    }
}

public class ShallowCopyExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("123 Main St", "New York");
        Person person1 = new Person("John", 30, address);

        // 进行浅拷贝
        Person person2 = (Person) person1.clone();

        // 修改 person2 的基本数据类型属性
        person2.age = 31;
        // 修改 person2 的引用数据类型属性
        person2.address.street = "456 Elm St";

        System.out.println("Person1 age: " + person1.age);
        System.out.println("Person1 address street: " + person1.address.street);
        System.out.println("Person2 age: " + person2.age);
        System.out.println("Person2 address street: " + person2.address.street);
    }
}

输出结果

Person1 age: 30
Person1 address street: 456 Elm St
Person2 age: 31
Person2 address street: 456 Elm St

从输出结果可以看出,修改 person2 的基本数据类型属性 age 不会影响 person1age,但修改 person2 的引用数据类型属性 addressstreet 会同时影响 person1addressstreet,这就是浅拷贝的特点。

2. 深拷贝(Deep Copy)

2.1 概念

深拷贝同样创建一个新对象,并且会递归地复制原对象的所有属性,包括基本数据类型和引用数据类型。对于引用数据类型,会创建一个新的对象并复制其内容,而不是简单地复制引用。因此,新对象和原对象的引用数据类型属性指向不同的内存地址,修改新对象中引用数据类型属性的内容不会影响原对象。

2.2 实现方式

实现深拷贝有多种方式,常见的是手动实现和使用序列化与反序列化。

手动实现

手动实现深拷贝需要在 clone() 方法中递归地复制引用数据类型的属性。

示例代码

class Address implements Cloneable {
    String street;
    String city;

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.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 clonedPerson = (Person) super.clone();
        // 手动复制引用数据类型属性
        clonedPerson.address = (Address) address.clone();
        return clonedPerson;
    }
}

public class DeepCopyExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("123 Main St", "New York");
        Person person1 = new Person("John", 30, address);

        // 进行深拷贝
        Person person2 = (Person) person1.clone();

        // 修改 person2 的基本数据类型属性
        person2.age = 31;
        // 修改 person2 的引用数据类型属性
        person2.address.street = "456 Elm St";

        System.out.println("Person1 age: " + person1.age);
        System.out.println("Person1 address street: " + person1.address.street);
        System.out.println("Person2 age: " + person2.age);
        System.out.println("Person2 address street: " + person2.address.street);
    }
}

输出结果

Person1 age: 30
Person1 address street: 123 Main St
Person2 age: 31
Person2 address street: 456 Elm St

从输出结果可以看出,修改 person2 的基本数据类型属性 age 和引用数据类型属性 addressstreet 都不会影响 person1,这就是深拷贝的特点。

使用序列化与反序列化

使用 Java 的序列化和反序列化机制也可以实现深拷贝。需要让类实现 Serializable 接口,然后通过 ObjectOutputStreamObjectInputStream 进行序列化和反序列化操作。

示例代码

import java.io.*;

class Address implements Serializable {
    String street;
    String city;

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

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 bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);

        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (Person) ois.readObject();
    }
}

public class DeepCopyWithSerializationExample {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Address address = new Address("123 Main St", "New York");
        Person person1 = new Person("John", 30, address);

        // 进行深拷贝
        Person person2 = person1.deepCopy();

        // 修改 person2 的基本数据类型属性
        person2.age = 31;
        // 修改 person2 的引用数据类型属性
        person2.address.street = "456 Elm St";

        System.out.println("Person1 age: " + person1.age);
        System.out.println("Person1 address street: " + person1.address.street);
        System.out.println("Person2 age: " + person2.age);
        System.out.println("Person2 address street: " + person2.address.street);
    }
}

这种方式的优点是代码简洁,不需要手动处理每个引用数据类型的复制;缺点是需要类实现 Serializable 接口,并且序列化和反序列化操作可能会影响性能。

3. 浅拷贝与深拷贝的区别

  • 基本数据类型:浅拷贝和深拷贝都会复制基本数据类型的值,修改新对象的基本数据类型属性不会影响原对象。
  • 引用数据类型:浅拷贝只复制引用,新对象和原对象的引用数据类型属性指向同一个内存地址,修改新对象的引用数据类型属性会影响原对象;深拷贝会创建新的对象并复制其内容,新对象和原对象的引用数据类型属性指向不同的内存地址,修改新对象的引用数据类型属性不会影响原对象。

综上所述,在选择使用浅拷贝还是深拷贝时,需要根据具体的业务需求来决定。如果不希望修改新对象影响原对象,应该使用深拷贝;如果只关心对象的基本数据类型属性,或者可以接受引用数据类型属性的共享,那么浅拷贝就足够了。

相关文章:

  • 微服务,服务治理nacos,负载均衡LOadBalancer,OpenFeign
  • Leetcode 662: 二叉树最大宽度
  • 大白话跨域问题的原理与多种解决方法的实现
  • 信息学奥赛一本通1009
  • el-table input textarea 文本域 自适应高度,切换分页滚动失效处理办法
  • 【Linux实践系列】:用c语言实现一个shell外壳程序
  • HTML + CSS 题目
  • C语言基础知识02
  • 安防监控/视频集中存储EasyCVR视频汇聚平台如何配置AI智能分析平台的接入?
  • 分布式锁—2.Redisson的可重入锁二
  • CSS - 妙用Sass
  • 字符串--子串匹配
  • Linux进程状态
  • BUUCTF [BJDCTF2020]EasySearch1
  • Android Flow 示例
  • Flutter 学习之旅 之 flutter 使用 flutter_screenutil 简单进行屏幕适配
  • 【算法】837. 连通块中点的数量
  • 后端-Java虚拟机
  • STM32引脚类型
  • 1.计算机基础知识
  • 普雷沃斯特当选新一任天主教罗马教皇
  • 两部门发布山洪灾害气象预警:北京西部、河北西部等局地山洪可能性较大
  • 首届上海老年学习课程展将在今年10月举办
  • 明星站台“胖都来”背后:百元起录视频,20万可请顶流
  • 应对美政策调整:中国重在开放与创新,维护好数据主权
  • 综合治理食品添加剂滥用问题,国务院食安办等六部门联合出手