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

每日一博 - Java的Shallow Copy和Deep Copy

文章目录

  • 概述
  • 创建对象的5种方式
    • 1. 通过new关键字
    • 2. 通过Class类的newInstance()方法
    • 3. 通过Constructor类的newInstance方法
    • 4. 利用Clone方法
    • 5. 反序列化
  • Clone方法
  • 基本类型和引用类型
  • 浅拷贝
  • 深拷贝
  • 如何实现深拷贝
    • 1. 让每个引用类型属性内部都重写clone()方法
    • 2. 利用序列化

在这里插入图片描述

概述

关于Java的深拷贝和浅拷贝,简单来说就是创建一个和已知对象一模一样的对象。可能日常编码过程中用得不多,但了解深拷贝和浅拷贝的原理,对于Java中的值传递或者引用传递将会有更深的理解。


创建对象的5种方式

1. 通过new关键字

最常用的一种方式,通过new关键字调用类的有参或无参构造方法来创建对象。比如Object obj = newObject()


2. 通过Class类的newInstance()方法

这种默认是调用类的无参构造方法创建对象。比如Artisan p2 =(Artisan)Class. forName("com. ys.artisan.Artisan").newInstance()


3. 通过Constructor类的newInstance方法

和第2种方法类似,都是通过反射来实现的。通过java.lang. relect. Constructor类的newInstance()方法指定某个构造器来创建对象

  Artisan.class.getConstructor()[0].newInstance();

实际上第2种方法利用Class的newInstance()方法创建对 象,其内部调用还是Constructor的newInstance()方法。


4. 利用Clone方法

Clone是Object类中的一个方法,clone克隆顾名思义就是创建一个一模一样的对象出来。通过对象A. clone()方法会创建一个内容和对象A一模一样的对象B

Artisan a1 = new Artisan();
Artisan a2 = a1.clone();

5. 反序列化

序列化是把堆内存中的Java对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络节点(在网络上传输)​。

而反序列化则是把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程


Clone方法

我们这里介绍Java的深拷贝和浅拷贝,其实现方式正是通过调用Object类的clone()方法来完成

   @IntrinsicCandidateprotected native Object clone() throws CloneNotSupportedException;

这是一个用native关键字修饰的方法,关于native关键字,不理解也没关系,只需要知道用native修饰的方法就是告诉操作系统去实现。

具体过程不需要了解,只需要知道clone方法的作用就是复制对象并产生一个新的对象。那么这个新的对象和原对象是什么关系呢?


基本类型和引用类型

先拉齐一个概念,在Java中基本类型和引用类型的区别。

在Java中数据类型可以分为两大类:基本类型和引用类型。

  • 基本类型也称为值类型,分别是字符类型char,布尔类型boolean以及数值类型byte、short、int、long、float、double。
  • 引用类型则包括类、接口、数组、枚举等

Java将内存空间分为堆和栈。基本类型直接在栈中存储数值,而引用类型是将引用放在栈中,实际存储的值是放在堆中,通过栈中的引用指向堆中存放的数据。

基本类型和引用类型在JVM存储结构如图

在这里插入图片描述

  • a和b都是基本类型,其值是直接存放在栈中的;
  • 而c和d是String声明的,这是一个引用类型,引用地址是存放在栈中,然后指向堆的内存空间。
  • d = c;这条语句表示将c的引用赋值给d,那么c和d将指向同一块堆内存空间

浅拷贝

浅拷贝会复制对象的基本字段值,但对于对象中的引用类型字段,浅拷贝仅复制引用地址,而不会创建新的对象实例。即拷贝后的对象与原对象共享相同的引用类型数据。

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;}// 浅拷贝实现,使用Object.clone()@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}class Address {String city;public Address(String city) {this.city = city;}
}

使用

Person person1 = new Person("Alice", 25, new Address("New York"));
Person person2 = (Person) person1.clone();// 修改 person2 的地址
person2.address.city = "Los Angeles";// person1 的 address 也会被改变,因为浅拷贝复制的是引用地址
System.out.println(person1.address.city);  // 输出 "Los Angeles"

调用对象的clone方法,必须要让类实现Cloneable接口,并且重写clone方法

创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。


深拷贝

弄清楚了浅拷贝后,深拷贝就很容易理解了。深拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当用户修改其中一个对象的任何内容时,都不会影响另一个对象的内容

深拷贝会递归地复制对象中的所有字段,包括引用类型字段所指向的对象。这样拷贝后的对象与原对象完全独立,互不影响。


如何实现深拷贝

深拷贝就是要让原始对象和克隆之后的对象所具有的引用类型属性不是指向同一块堆内存

1. 让每个引用类型属性内部都重写clone()方法

既然引用类型不能实现深拷贝,那么将每个引用类型都拆分为基本类型,分别进行浅拷贝。比如上面的例子,Person类有一个引用类型Address(其实String也是引用类型,但是String类型有点特殊)​,在Address类内部也重写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;}// 深拷贝实现@Overrideprotected Object clone() throws CloneNotSupportedException {Person cloned = (Person) super.clone();cloned.address = new Address(this.address.city);  // 递归复制引用类型字段return cloned;}
}

使用

Person person1 = new Person("Alice", 25, new Address("New York"));
Person person2 = (Person) person1.clone();// 修改 person2 的地址
person2.address.city = "Los Angeles";// person1 的 address 不会改变,因为深拷贝创建了独立的引用
System.out.println(person1.address.city);  // 输出 "New York"

这种做法有个弊端,这里Person类只有一个Address引用类型,而Address类没有,所以这里只重写Address类的clone方法,但是如果Address类也存在一个引用类型,那么也要重写其clone方法,这样有多少个引用类型,就要重写多少次,如果存在很多引用类型,那么代码量显然会很大,所以这种方法不太合适


2. 利用序列化

序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取出来。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在JVM中,所以可以利用对象的序列化产生克隆对象,然后通过反序列化获取这个对象。

注意每个需要序列化的类都要实现Serializable接口,如果有某个属性不需要序列化,可以将其声明为transient,即将其排除在克隆属性之外

因为序列化产生的是两个完全独立的对象,所有无论嵌套多少个引用类型,序列化都是能实现深拷贝的

首先,确保要进行深拷贝的类和其引用的类都实现了Serializable接口

import java.io.*;// 需要进行深拷贝的类必须实现 Serializable 接口
class Address implements Serializable {private static final long serialVersionUID = 1L;String city;public Address(String city) {this.city = city;}@Overridepublic String toString() {return "Address{" +"city='" + city + '\'' +'}';}
}class Person implements Serializable {private static final long serialVersionUID = 1L;String name;int age;Address address;public Person(String name, int age, Address address) {this.name = name;this.age = age;this.address = address;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +", address=" + address +'}';}// 深拷贝方法:使用序列化和反序列化public Person deepCopy() {try {// 将对象写入字节流ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);oos.flush();// 从字节流读取对象ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);return (Person) ois.readObject();} catch (IOException | ClassNotFoundException e) {e.printStackTrace();return null;}}
}public class DeepCopyExample {public static void main(String[] args) {// 创建原始对象Person person1 = new Person("Alice", 25, new Address("New York"));// 进行深拷贝Person person2 = person1.deepCopy();// 修改 person2 的地址if (person2 != null) {person2.address.city = "Los Angeles";person2.name = "Bob";}// 输出原对象和拷贝对象,验证深拷贝System.out.println("Original person1: " + person1);System.out.println("Copied person2: " + person2);}
}

输出结果:

Original person1: Person{name='Alice', age=25, address=Address{city='New York'}}
Copied person2: Person{name='Bob', age=25, address=Address{city='Los Angeles'}}
  • deepCopy()方法通过序列化将对象写入到ByteArrayOutputStream,再通过ObjectInputStream从字节流中读取对象,生成新的实例。
  • 修改person2的引用类型字段(如address.city)不会影响person1,从而验证了深拷贝的效果。

在这里插入图片描述

相关文章:

每日一博 - Java的Shallow Copy和Deep Copy

文章目录 概述创建对象的5种方式1. 通过new关键字2. 通过Class类的newInstance()方法3. 通过Constructor类的newInstance方法4. 利用Clone方法5. 反序列化 Clone方法基本类型和引用类型浅拷贝深拷贝如何实现深拷贝1. 让每个引用类型属性内部都重写clone()方法2. 利用序列化 概述…...

.netcore + postgis 保存地图围栏数据

一、数据库字段 字段类型选择(Type) 设置对象类型为:geometry 二、前端传递的Json格式转换 前端传递围栏的各个坐标点数据如下: {"AreaRange": [{"lat": 30.123456,"lng": 120.123456},{"lat": 30.123456…...

【AI图像生成网站Golang】项目介绍

AI图像生成网站 目录 一、项目介绍 二、雪花算法 三、JWT认证与令牌桶算法 四、项目架构 五、图床上传与图像生成API搭建 六、项目测试与调试(等待更新) 简介 本教程将手把手教你如何从零开始构建一个简单的AI图像生成网站。网站主要包含用户注册、图像生成、分类管理等…...

对称加密算法DES的实现

一、实验目的 1、了解对称密码体制基本原理 2、掌握编程语言实现对称加密、解密 二、实验原理 DES 使用一个 56 位的密钥以及附加的 8 位奇偶校验位,产生最大 64 位的分组大小。这是一个迭代的分组密码,使用称为 Feistel 的技术,其中将加密…...

Spring Boot 启动时修改上下文

Spring Boot 启动时修改上下文 为了让项目在启东时&#xff0c;加载到封装的JAR中的国际化文件在封装JAR是增加以下配置类可用于更改启动上下文中的信息依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoco…...

传奇996_19——常用函数

打印 打印到公告 lua版 sendmsg(*actor*, ConstCfg.notice.own, {"Msg":"<font color\#ff0000\>即将更新属性2222&#xff01;&#xff01;&#xff01;</font>","Type":9}) sendmsg(*actor*, 1, {"Msg":"<fon…...

计算机毕业设计Python+Neo4j知识图谱医疗问答系统 大模型 机器学习 深度学习 人工智能 大数据毕业设计 Python爬虫 Python毕业设计

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…...

【Python】如何设置VSCode中的Pylint,消除各种没有必要的警告

前言 最近打开VSCode&#xff0c;编辑之前创建的Python项目&#xff0c;突然发现多了一堆报错和警告&#xff0c;如下图所示。 就非常吓人&#xff0c;因为之前这个项目是没有任何报错的&#xff0c;我赶紧试着运行了一下&#xff0c;还好&#xff0c;可以正常运行&#xff0c;…...

游戏引擎学习第14天

视频参考:https://www.bilibili.com/video/BV1iNUeYEEj4/ 1. 为什么关注内存管理&#xff1f; 内存分配是潜在的失败点&#xff1a; 每次进行内存分配&#xff08;malloc、new等&#xff09;时&#xff0c;都可能失败&#xff08;例如内存不足&#xff09;。这种失败会引入不稳…...

关于mysql中的锁

mysql中包含的锁分为&#xff1a; 一、全局锁 二、表锁 三、行锁 一、全局锁 全局锁的力度是最大的&#xff0c;全局锁对整个数据库实例加锁&#xff0c;加锁后整个实例就处于只读状态&#xff0c;后续的DML的写语句&#xff0c;DDL语句&#xff0c;已经更新操作的事务提交语句…...

机器学习-4:机器学习的建模流程

机器学习的建模流程 流程为&#xff1a; 原始数据 --> 数据预处理 --> 特征工程 --> 建模 --> 验证。 原始数据收集 所有AI或机器学习的基础就是数据&#xff0c;没有数据就什么都做不了&#xff0c;在搭建一个系统之前首要考虑的就是有没有足够多的数据可以支撑这…...

Android 6年经验面试总结 2024.11.15

背景&#xff1a;深圳 面过12家中大厂、4家中小厂&#xff0c;通过4家中大厂&#xff0c;2家offer。 针对六年的求职面试总结&#xff1a;项目经验70%30%基础&#xff08;基础应该必会&#xff09; 对于上来就问八股文的公司&#xff0c;对于已经工作了5年以上的开发来说&…...

R语言数据分析可视化——summarytools包的使用

R语言中的summarytools包通过提供能够用最少的代码生成数据全面摘要的功能,使数据分析更加简单。summarytools包提供了一种简单的方法来生成数据集的摘要统计信息,包括描述性统计、频率表、交叉表、缺失值、异常值、相关性、线性回归、ANOVA、卡方检验等。本文将介绍如何使用…...

转型一年半,虎牙直播的第二增长曲线喜忧参半

文&#xff1a;互联网江湖 作者&#xff1a;刘致呈 最近&#xff0c;虎牙公司&#xff08;NYSE:HUYA&#xff09;公布了2024年第三季度财报。 表现怎么样呢&#xff1f;从财务数据上看&#xff0c;这份成绩单有点不尽人意。 报告期内&#xff0c;虎牙实现营收15.38亿元&…...

makefile笔记

makefile 在 Makefile 中&#xff0c;预定义的变量&#xff08;也称为内置变量&#xff09;提供了对构建过程中的默认值和特殊值的访问。这些变量通常由 Make 自动设置&#xff0c;并且可以覆盖它们以改变 Make 的行为。下面是 Make 环境中常见的几个内置变量及其用途&#xf…...

Rewar Model的输出(不包含训练)

这里写自定义目录标题 介绍模型推理的输出过程方案原始Token输出RM输出&#xff08;回归任务&#xff09; 介绍 奖励函数模型 (Reward Model) 是人工智能 (AI) 中的一种方法&#xff0c;模型因其对给定提示的响应而获得奖励或分数。现在的文章清一色的讲解RM的训练&#xff0c…...

Python调用API翻译Excel中的英语句子并回填数据

一、问题描述 最近遇到一个把Excel表中两列单元格中的文本读取&#xff0c;然后翻译&#xff0c;再重新回填到单元格中的案例。大约有700多行&#xff0c;1400多个句子&#xff0c;一个个手动复制粘贴要花费不少时间&#xff0c;而且极易出错。这时&#xff0c;我们就可以请出…...

SQL面试题——抖音SQL面试题 最大在线用户数

最大在线用户数 下面的数据记录了一个直播平台上用户进入平台和离开平台的情况 +---+-------------------+-----+ | id| etime| type| +---+-------------------+-----+ | 1|2021-06-10 10:00:00|enter| | 1|2021-06-10 19:00:00|leave| | 2|2021-06-10 11:0…...

前端知识点---Window对象(javascript)了解

Window对象 在JavaScript中&#xff0c;当你在非严格模式下的全局作用域中使用this时&#xff0c;它会引用全局对象。在浏览器环境中&#xff0c;这个全局对象就是Window。 01什么是 Window 对象&#xff1f; Window 是浏览器提供的一个全局对象&#xff0c;它代表了浏览器的…...

llama factory lora 微调 qwen2.5 7B Instruct模型

项目背景 甲方提供一台三卡4080显卡 需要进行qwen2.5 7b Instruct模型进行微调。以下为整体设计。 要使用 LLaMA-Factory 对 Qwen2.5 7B Instruct模型 进行 LoRA&#xff08;Low-Rank Adapters&#xff09;微调&#xff0c;流程与之前提到的 Qwen2 7B Instruct 模型类似。LoRA …...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

技术栈RabbitMq的介绍和使用

目录 1. 什么是消息队列&#xff1f;2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

基于Springboot+Vue的办公管理系统

角色&#xff1a; 管理员、员工 技术&#xff1a; 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能&#xff1a; 该办公管理系统是一个综合性的企业内部管理平台&#xff0c;旨在提升企业运营效率和员工管理水…...