当前位置: 首页 > 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 …...

synchronized 学习

学习源&#xff1a; https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖&#xff0c;也要考虑性能问题&#xff08;场景&#xff09; 2.常见面试问题&#xff1a; sync出…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

django filter 统计数量 按属性去重

在Django中&#xff0c;如果你想要根据某个属性对查询集进行去重并统计数量&#xff0c;你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求&#xff1a; 方法1&#xff1a;使用annotate()和Count 假设你有一个模型Item&#xff0c;并且你想…...

质量体系的重要

质量体系是为确保产品、服务或过程质量满足规定要求&#xff0c;由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面&#xff1a; &#x1f3db;️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限&#xff0c;形成层级清晰的管理网络&#xf…...

PL0语法,分析器实现!

简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

Linux系统部署KES

1、安装准备 1.版本说明V008R006C009B0014 V008&#xff1a;是version产品的大版本。 R006&#xff1a;是release产品特性版本。 C009&#xff1a;是通用版 B0014&#xff1a;是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存&#xff1a;1GB 以上 硬盘&#xf…...

【安全篇】金刚不坏之身:整合 Spring Security + JWT 实现无状态认证与授权

摘要 本文是《Spring Boot 实战派》系列的第四篇。我们将直面所有 Web 应用都无法回避的核心问题&#xff1a;安全。文章将详细阐述认证&#xff08;Authentication) 与授权&#xff08;Authorization的核心概念&#xff0c;对比传统 Session-Cookie 与现代 JWT&#xff08;JS…...

高抗扰度汽车光耦合器的特性

晶台光电推出的125℃光耦合器系列产品&#xff08;包括KL357NU、KL3H7U和KL817U&#xff09;&#xff0c;专为高温环境下的汽车应用设计&#xff0c;具备以下核心优势和技术特点&#xff1a; 一、技术特性分析 高温稳定性 采用先进的LED技术和优化的IC设计&#xff0c;确保在…...

新版NANO下载烧录过程

一、序言 搭建 Jetson 系列产品烧录系统的环境需要在电脑主机上安装 Ubuntu 系统。此处使用 18.04 LTS。 二、环境搭建 1、安装库 $ sudo apt-get install qemu-user-static$ sudo apt-get install python 搭建环境的过程需要这个应用库来将某些 NVIDIA 软件组件安装到 Je…...