java,深拷贝和浅拷贝

ops/2024/10/18 9:22:12/

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

目录

一、浅拷贝(Shallow Copy)

实现方式

二、深拷贝(Deep Copy)

实现方式

1、手动深拷贝

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

拷贝中的注意事项

拷贝的应用场景

总结


一、拷贝(Shallow Copy)

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

实现方式

通过 clone() 方法实现浅拷贝。需要类实现 Cloneable 接口,并重写 clone() 方法。

java">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;}@Overrideprotected 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() 或其他方式来复制子对象。每个引用类型字段的深拷贝都需要单独处理。

以一个简单的对象层次结构为例:

java">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;}@Overrideprotected Object clone() throws CloneNotSupportedException {Person cloned = (Person) super.clone();cloned.address = (Address) this.address.clone();  // 深拷贝 addressreturn cloned;}
}class Address implements Cloneable {String city;public Address(String city) {this.city = city;}@Overrideprotected 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)。
  • 反序列化:从存储介质中读取字节流,并重新构建对象及其引用对象,从而生成与原始对象完全独立的新对象。
java">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)复杂对象结构:当对象中嵌套了其他对象,并且这些对象可能会被修改时(例如树结构、图结构),深拷贝确保了各个层次的对象都能保持独立。

总结

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

http://www.ppmy.cn/ops/112511.html

相关文章

UDP协议

我们已经知道 UDP 具有无连接,不可靠传输,面向数据报,全双工的特点,接下来就来具体看看 UDP 协议报文结构。 UDP 协议属于内核协议栈,在底层 UDP 的报头是一个 C 语言的位段类型的结构体: struct udphdr {…

Android 提高第一次开机速度(取消系统默认手机加密)

Android刷机后第一次开机,系统默认是要手机加密的,有些机器加密就几秒钟,有些机器加密要30多秒甚至更长,严重影响了开机速度。 修改路径: device/qcom/xxxx/fstab_AB_variant.qcom 有一些是这个文件: devic…

opencv之图像梯度

图像梯度 图像梯度计算的是图像变化的速度。对于图像的边缘部分,其灰度值变化较大,梯度值也较大;相反,对于图像中比较平滑的部分,其灰度值变化较小,相应的梯度值也较小。一般情况下,图像梯度计…

Android使用LiquidFun物理引擎实现果冻碰撞效果

一、效果展示 Android使用LiquidFun物理引擎实现果冻碰撞效果 二、LiquidFun物理引擎简介 LiquidFun是一个由Google开发并开源的2D物理模拟库,它基于Box2D物理引擎,并扩展了流体模拟的功能。 流体动力学模拟:LiquidFun提供了强大的流体动力学…

Redhat 8/9 缺少 compat-db47

一、简介 通过官网公告,可以查看到已经删除了compat-db47这个软件包,但是运行某些软件,还需要这个软件,所以下面介绍一下编译安装的方法。 二、下载 wget https://download.oracle.com/berkeley-db/db-4.7.25.tar.gz三、解压安…

【机器学习(三)】分类和回归任务-随机森林-Sentosa_DSML社区版

文章目录 一、算法概念二、算法原理(一)定义(二)袋外数据 三、随机森林的优缺点(一)优点(二)缺点 四、随机森林分类任务实现对比(一)数据加载1、Python代码2、…

【网络安全】Node.js初探+同步异步进程

未经许可,不得转载。 文章目录 Node.js 基础介绍NPM 包管理安装同步与异步fs 模块示例child_process 模块Node.js 基础介绍 Node.js 是运行在服务器端的 JavaScript 环境。它基于 Chrome 的 V8 引擎,拥有高效的执行性能。Node.js 采用事件驱动的 I/O 模型,使得它在处理高并…

Redis 篇-初步了解 Redis 持久化、Redis 主从集群、Redis 哨兵集群、Redis 分片集群

🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录 1.0 分布式缓存概述 2.0 Redis 持久化 2.1 RDB 持久化 2.1.1 RDB 的 fork 原理 2.2 AOF 持久化 2.3 RDB 与 AOF 之间的区别 3.0 Redis 主从集群 3.1 搭建主从集群 3.2…