Java 克隆技术详解,深拷贝与浅拷贝的区别及实现
什么是克隆,为什么在编程中使用克隆
克隆是指创建一个对象的副本,使得新创建的对象在内容上与原始对象相同。在编程中,克隆是常用的技术之一,它具有以下几个重要用途和优势:
-
复制对象:使用克隆可以创建一个与原始对象相同的新对象,包括对象的属性和状态。这样可以在不影响原始对象的情况下,对新对象进行修改、操作、传递等。这在某些场景下非常有用,可以避免重新创建和初始化一个对象。
-
隔离性与保护:通过克隆,可以创建一个独立于原始对象的副本。这样,修改克隆对象时,不会影响到原始对象,从而实现了对象之间的隔离性。这对于多线程环境下的并发操作或者保护重要数据具有重要意义。
-
性能优化:有时候,通过克隆对象可以提高程序的性能。在某些场景下,对象的创建和初始化过程可能较为耗时,如果需要多次使用这个对象,通过克隆原始对象可以避免重复的创建和初始化过程,从而提高程序的执行效率。
-
原型模式:克隆在设计模式中有一个重要的角色,即原型模式。原型模式通过克隆来创建对象的实例,而不是使用传统的构造函数。这样可以提供更灵活的对象创建方式,并且避免了频繁的子类化。
在编程中,通常通过实现Cloneable接口和重写clone方法来实现对象的克隆。然而,需要注意的是克隆操作可能存在深拷贝和浅拷贝的区别,在使用时需要根据实际需求选择合适的克隆方式。
什么是深拷贝和浅拷贝
深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是在克隆(Clone)操作中经常遇到的两个概念,它们描述了克隆操作对于对象内部引用的处理方式。
-
浅拷贝(Shallow Copy):
- 浅拷贝指在克隆操作中,只复制对象本身以及对象内部的基本数据类型的属性,而不复制对象内部的引用类型的属性。
- 浅拷贝仅仅创建了一个新对象,该对象与原始对象共享对同一引用类型属性的访问。如果原始对象的引用类型属性被修改,浅拷贝的对象也会受到影响。
- 在浅拷贝中,新对象和原始对象指向同一块内存区域,因此对其中一个对象进行修改可能会影响到另一个对象。
-
深拷贝(Deep Copy):
- 深拷贝指在克隆操作中,除了复制对象本身以及对象内部的基本数据类型的属性外,还要递归地复制对象内部的引用类型的属性。即深度克隆了所有引用类型的属性。
- 深拷贝创建了一个完全独立的新对象,该对象与原始对象没有任何关联,对新对象和原始对象的修改互不影响。
- 在深拷贝中,新对象和原始对象分别对应不同的内存区域,它们之间不存在引用关系,因此修改其中一个对象不会影响到另一个对象。
为了实现深拷贝,需要对对象内部的引用类型属性进行递归复制。常见的实现深拷贝的方式包括:
- 通过序列化和反序列化:将对象序列化为字节流,然后再反序列化为新的对象,这样可以创建一个与原始对象完全独立的副本。
- 通过逐个复制引用类型属性:对于每个引用类型的属性,创建一个新的实例并将原始对象属性的内容复制到新的实例中。
需要注意的是,并非所有对象都能进行深拷贝。某些对象或者类中的属性可能是不可变的,无需拷贝;某些对象可能包含循环引用,无法完全复制。因此,在进行克隆操作时,需要根据具体情况选择合适的拷贝方式。
深拷贝和浅拷贝的主要区别在于对于对象内部引用类型属性的处理方式。
-
数据复制层次的深度:
- 浅拷贝只复制对象本身以及对象内部的基本数据类型的属性,不会递归地复制引用类型的属性。因此,在浅拷贝中,新对象和原始对象共享对同一引用类型属性的访问。
- 深拷贝除了复制对象本身和基本数据类型的属性外,还会递归地复制对象内部的引用类型的属性。这样,深拷贝创建了一个完全独立的新对象,与原始对象没有任何关联。
-
对象之间的关联性:
- 浅拷贝得到的新对象与原始对象共享对同一引用类型属性的访问。如果对其中一个对象的引用类型属性进行修改,另一个对象也会受到影响。
- 深拷贝得到的新对象与原始对象没有任何关联,修改其中一个对象的引用类型属性不会影响到另一个对象。
-
内存区域的分配:
- 在浅拷贝中,新对象和原始对象指向同一块内存区域。因此,对其中一个对象进行修改可能会影响到另一个对象。
- 在深拷贝中,新对象和原始对象分别对应不同的内存区域,它们之间不存在引用关系,因此修改其中一个对象不会影响到另一个对象。
浅拷贝示例
实现 Cloneable 接口和重写 clone() 方法:
- Java 中的 Cloneable 接口是一个标记接口,没有定义任何方法。通过实现 Cloneable 接口并重写 clone() 方法,可以实现对象的浅拷贝。
- 在 clone() 方法中,调用父类的 clone() 方法,并将其返回值进行类型转换即可完成浅拷贝。
下面是一个示例代码,演示了如何使用 Cloneable 接口和 clone() 方法实现浅拷贝:
class Person implements Cloneable {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public int getAge() {return age;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}public class Main {public static void main(String[] args) {Person person1 = new Person("Alice", 25);try {// 浅拷贝Person person2 = (Person) person1.clone();System.out.println(person1.getName() + " " + person1.getAge()); // Alice 25System.out.println(person2.getName() + " " + person2.getAge()); // Alice 25person2.setName("Bob");person2.setAge(30);System.out.println(person1.getName() + " " + person1.getAge()); // Alice 25System.out.println(person2.getName() + " " + person2.getAge()); // Bob 30} catch (CloneNotSupportedException e) {e.printStackTrace();}}
}
在上述示例中,我们创建了一个 Person 类,并实现了 Cloneable 接口。在 clone() 方法中直接调用了父类的 clone() 方法,并进行了类型转换。通过调用 clone()
方法,可以得到一个新的对象 person2
,它与原始对象 person1
具有相同的属性值。当修改 person2
的属性时,不会影响到 person1
。
深拷贝示例
使用序列化和反序列化:
- 将对象写入到字节流中,然后再从字节流中读取出来,这个过程会重新创建一个完全独立的对象,实现了深拷贝。
- 为了实现深拷贝,需要将对象及其关联的对象都实现序列化。
下面是一个示例代码,演示了如何使用序列化和反序列化实现深拷贝:
import java.io.*;class Address implements Serializable {private String city;private String street;public Address(String city, String street) {this.city = city;this.street = street;}public void setCity(String city) {this.city = city;}public void setStreet(String street) {this.street = street;}public String getCity() {return city;}public String getStreet() {return street;}
}class Person implements Serializable {private String name;private int age;private Address address;public Person(String name, int age, Address address) {this.name = name;this.age = age;this.address = address;}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}public void setAddress(Address address) {this.address = address;}public String getName() {return name;}public int getAge() {return age;}public Address getAddress() {return address;}
}public class Main {public static void main(String[] args) {Address address = new Address("City", "Street");Person person1 = new Person("Alice", 25, address);// 深拷贝Person person2 = deepCopy(person1);System.out.println(person1.getName() + " " + person1.getAge() + " " + person1.getAddress().getCity()); // Alice 25 CitySystem.out.println(person2.getName() + " " + person2.getAge() + " " + person2.getAddress().getCity()); // Alice 25 Cityperson2.setName("Bob");person2.setAge(30);person2.getAddress().setCity("New City");System.out.println(person1.getName() + " " + person1.getAge() + " " + person1.getAddress().getCity()); // Alice 25 CitySystem.out.println(person2.getName() + " " + person2.getAge() + " " + person2.getAddress().getCity()); // Bob 30 New City}public static <T extends Serializable> T deepCopy(T object) {try {ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);objectOutputStream.writeObject(object);objectOutputStream.flush();ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);return (T) objectInputStream.readObject();} catch (IOException | ClassNotFoundException e) {e.printStackTrace();return null;}}
}
在上述示例中,我们创建了一个 Address 类和一个 Person 类,它们都实现了 Serializable 接口。通过序列化和反序列化操作,我们可以实现深拷贝。在 deepCopy()
方法中,我们使用字节流将对象写入到内存中,并从内存中读取出来,从而得到一个新的独立对象。通过调用 deepCopy()
方法,可以得到一个新的对象 person2
,它与原始对象 person1
完全独立。在修改 person2
的属性时,不会影响到 person1
。
值得注意的是,要实现深拷贝,所有相关的类都需要实现 Serializable 接口。
深拷贝和浅拷贝的区别
深拷贝(Deep Copy):
-
适用场景:
- 当源对象包含引用类型的属性时,如果需要复制对象及其子对象的所有属性,而不仅仅只是复制引用,就需要使用深拷贝。
- 当希望修改副本对象的属性不影响原始对象时,需要使用深拷贝。
-
工作原理:
- 深拷贝将源对象及其关联的全部对象进行递归复制,每个对象都拥有独立的内存空间,修改副本对象不会影响原始对象。
-
实现方式:
- 使用递归或者拷贝构造函数来复制对象及其子对象的属性。
-
示例场景:
- 复制复杂对象的副本,使其成为独立的个体,例如:拷贝一个包含集合、嵌套对象等的数据结构。
- 对象图的克隆,当原对象包含子对象,并且对子对象的修改不应该影响原对象时。
浅拷贝(Shallow Copy):
-
适用场景:
- 当源对象的属性全为基本数据类型或者不可变对象,并且不需要复制引用类型的属性时,可以使用浅拷贝。
- 当希望修改副本对象的属性同时影响原始对象时,可以使用浅拷贝。
-
工作原理:
- 浅拷贝只复制对象及其引用,而不复制引用指向的实际对象,新旧对象将共享同一个引用对象。修改副本对象会影响原始对象。
-
实现方式:
- 通常使用对象的
clone()
方法来进行浅拷贝。
- 通常使用对象的
-
示例场景:
- 快速创建对象副本,以便在某些操作中对其进行修改,同时保留原始对象。
- 在某些情况下,共享一部分数据以节省内存和提高性能。
相关文章:
Java 克隆技术详解,深拷贝与浅拷贝的区别及实现
什么是克隆,为什么在编程中使用克隆 克隆是指创建一个对象的副本,使得新创建的对象在内容上与原始对象相同。在编程中,克隆是常用的技术之一,它具有以下几个重要用途和优势: 复制对象:使用克隆可以创建一个…...
包装器function
std::function模板类是一个通用的可调用对象的包装器,用简单的、统一的方式处理可调用对象。 template<class _Fty> class function…… _Fty是可调用对象的类型,格式:返回类型(参数列表)。 包含头文件:#include <functi…...

Django Rest_Framework(三)
文章目录 1. 认证Authentication2. 权限Permissions使用提供的权限举例自定义权限 3. 限流Throttling基本使用可选限流类 4. 过滤Filtering5. 排序Ordering6. 分页Pagination可选分页器 7. 异常处理 ExceptionsREST framework定义的异常 8. 自动生成接口文档coreapi安装依赖设置…...

总结 IO、存储、硬盘、文件系统相关常识
目录 一、IO是什么? 二、存储 三、硬盘 四、文件系统 4.1 文件目录和组织方式 4.2 文化路径 4.3 文件类型 4.4 文件系统操作 一、IO是什么? IO是英文Input/Output的缩写,指输入/输出。在计算机科学中,IO通常指计算机与外部设备或…...
JavaScript、深入浅出Node.js前端技能汇总
JavaScript 前端技能汇总 Frontend Knowledge Structure 深入浅出Node.js 书籍pdf 《深入浅出Node.js》的相关代码 Javascript&jQuery教程 pdf html & css教程 pdf 高性能JavaScript_中英双语版 pdf 跳坑之js调用另一个js文件中函数 方法1; 在html文件中加入两…...
use gnustep objective-c
first app #import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {NSAutoreleasePool *pool [NSAutoreleasePool new];NSLog("first start");[pool drain];return 0; }tech 专注于概念,而不是迷失在语言技术细节中编程语言…...

8.15锁的优化
1.锁升级(锁膨胀) 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁 偏向锁:不是真的加锁,而是做了一个标记,如果有别的线程来竞争才会真的加锁,如果没有别的线程竞争就不会加锁. 轻量级锁:一个线程占领锁资源后,另一个线程通过自旋的方式反复确认锁是否被是否(这个过程比较…...

单片机复位电路分析
来分析一下这个电路: 首先这里面有电容,所以是一个动态电路。哈哈哈 假设左上角的电压源是5V的代号为VOLT。 可以知道电容capacitor C1左边的电压也是5V,电容中间隔着一个绝缘体,所以不导电, 这个时候电流无法通过…...

公文写作技巧:“三面镜子”写作提纲60例
写作技巧:“三面镜子”写作提纲60例 1. 用好“三面镜子” 推深做实警示教育 勤用“反光镜”以案为鉴。 善用“显微镜”以案明纪。 巧用“聚光镜”以案促改。 2. 年轻干部要用好“三面镜子” 用好“反光镜”,照亮基层中的“暗点” 用好“显微镜”&am…...
useEffect中的函数会执行2次原因
一、useEffect介绍 useEffect是React18的新特性,表示React的生命周期Hooks组件。等价于Claas组件的componentDidMount、componentDidUpdate,useEffect的返回函数等价于componentWillUnmount。(组件卸载、重新挂载都会触发这个函数,…...

更新k8s环境支付系统支付证书
目录 一、背景 二、更新支付系统银行证书 三、备份旧的secret信息 四、更新支付应用的证书信息 五、重启支付系统的应用 六、验证应用实例挂载的秘钥已更新 一、背景 支付系统是基于k8s容器化部署的微服务,支付系统使用的支付证书以及和银行有关的证书都是保存…...
C#的yield
在 C# 中,yield 关键字用于定义迭代器方法(Iterator Methods),并使其返回一个可枚举的序列。通过使用 yield 关键字,可以简化迭代器的实现,使其更加直观和易于理解。 使用 yield 关键字定义的方法被称为迭…...

外卖多门店小程序开源版开发
外卖多门店小程序开源版开发 外卖多门店小程序开源版的开发可以按照以下步骤进行: 确定需求:明确外卖多门店小程序的功能和特点,包括用户注册登录、浏览菜单、下单支付、订单管理等。技术选型:选择适合开发小程序的技术框架&…...
打印图案、
描述 请编写一个程序,打印下面的图案: 输入 无 输出 打印上述图案 输入样例 1 无 输出样例 1 * * * * * * * * * * * * * * * * * * * * * * * * * 代码一(如下):直接输出 #include <iostream> usin…...

# Windows 环境下载 Android 12源码
前言 Android 官网(该方式不适合 Windows 平台):https://source.android.com/source/downloading.html (备注自 2021 年 6 月 22 日起,安卓操作系统不再支持在 Windows 或 MacOS 上进行构建,如果要编译源码推荐先安装…...
【运维面试】Docker技术面试题总结
【运维面试】Docker技术面试题总结 一、Docker的基础概念1.1 什么是Docker?它可以为我们提供哪些便利?1.2 Docker的优点是什么?1.3 Docker的镜像是什么?1.4 Docker的数据卷是什么?1.5 Docker Compose是什么?1.6 Docker Swarm是什么?1.7 Docker Hub是什么?有哪些用途?1…...

CNN成长路:从AlexNet到EfficientNet(01)
一、说明 在 10年的深度学习中,进步是多么迅速!早在 2012 年,Alexnet 在 ImageNet 上的准确率就达到了 63.3% 的 Top-1。现在,我们超过90%的EfficientNet架构和师生训练(teacher-student)。 如果我们在 Ima…...

使用IDEA操作Mysql数据库
idea中自带了关于数据库的连接 首先要确保你的MySQL正在运行中 打开idea找到database( view —> Tool Windows —> database),大家也可以定个快捷键,方便以后日常操作 就是这个样子,然后点加号 然后就可以编写执…...

ChatGPT下架官方检测工具,承认无法鉴别AI内容
去年底,OpenAI 推出的 ChatGPT ,带来了生成式人工智能涌现的热潮。它不仅能够协助完成撰写邮件、视频脚本、文案、翻译、代码等任务,还能通过学习和理解人类的语言来进行对话,并根据聊天的上下文进行互动。 但随之而来的争议也让人…...
Java通过实例调用getClass()方法、类名.class操作、通过运行时类获取其它信息
说明 Java Object类的getClass()函数,是通过对象调用的,是一个实例方法,该方法返回当前对象的运行时类。 通过类名.class可以获得和通过实例调用getClass()函数一样的信息。 获得运行时类以后,可以进一步获取其它信息。 代码示例…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...

自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...