【Java 学习】对象赋值的艺术:Java中clone方法的浅拷贝与深拷贝解析,教你如何在Java中实现完美复制
💬 欢迎讨论:如对文章内容有疑问或见解,欢迎在评论区留言,我需要您的帮助!
👍 点赞、收藏与分享:如果这篇文章对您有所帮助,请不吝点赞、收藏或分享,谢谢您的支持!
🚀 传播技术之美:期待您将这篇文章推荐给更多对需要学习Java语言、低代码开发感兴趣的朋友,让我们共同学习、成长!
# 1. 什么是自定义类型的赋值?
什么是赋值呢?```java// 创建一个变量a,值为10int a = 10;// 把a的值赋值给bint b = a;// 他们两个的值一样System.out.println("a: "+a);System.out.println("b: "+ b);
int
是Java中的内置类型,我们自己创建的自定义类型(类)可以赋值吗?
同学们看一下,判断这是我们自定义类型的赋值吗?
class Student{public String name;public int age;public Student(String name, int age){this.name = name;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}public class Main {public static void main(String[] args){// 创建一个对象Student s1 = new Student("李华",18);// 把 s1的值赋值给s2Student s2 = s1;System.out.println(s1.toString());System.out.println(s2.toString());}
}
答: 不是的,在上面的代码中,s1
和s2
指向的是一个空间,s2
并没有属于自己的空间。
如图:
我们想要的是:s2
有自己的空间,并且空间的内容和s1
一样(如下图)
自定义类型的赋值是:申请一个自己单独能管理的空间。
想要完成 “完美” 的赋值,就需要使用clone()
方法
2. clone 方法
2.1 接口Cloneable 和 Object的中的clone方法
Cloneable
接口:
只有实现了 Cloneable 接口的类才可以正常调用 clone() 方法。否则会抛出 CloneNotSupportedException 异常。
Cloneable
接口文档如下
重写 clone()
方法:
Object 类的 clone() 方法是 protected,所以在自定义类中重写时,必须将其访问修饰符改为 public,才能从外部访问。
Object
类中的Clone
方法:
总结:
当一个类需要写clone
方法时,必须实现接口Cloneable
,并且重写Object
类中的clone
方法。
如果想要更详细的了解Object
类中的Clone
方法,可以参考 Object 类
2.2 用clone 方法解决标题1的问题
标题1的问题很严重,因为两个引用类型都指向一个空间,那么,当其中一个对象修改属性时另一个对象的属性也会被修改
public class Main {public static void main(String[] args){// 创建一个对象Student s1 = new Student("李华",18);// 把 s1的值赋值给s2Student s2 = s1;System.out.println(s1.toString());System.out.println(s2.toString());// 修改s1的name,s2的name也修改了s1.name = "王小明";System.out.println("只修改s1的name为 王小明");System.out.println(s1.toString());System.out.println(s2.toString());}
}
我们要做的是,进行赋值时给s2
也开一个空间
需要在让Student
类实现Cloneable
接口,重写Object
中的Clone
方法,并且调Clone
方法。
class Student implements Cloneable{public String name;public int age;public Student(String name, int age){this.name = name;this.age = age;}@Overridepublic Student clone() throws CloneNotSupportedException{return (Student)super.clone();}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}public class Main {public static void main(String[] args){try {// 创建一个对象Student s1 = new Student("李华",18);// 把 s1的值赋值给s2Student s2 = s1.clone(); // 调用object中的clone方法System.out.println(s1.toString());System.out.println(s2.toString());// 修改s1的name,s2的name也修改了s1.name = "王小明";System.out.println("只修改s1的name为 王小明");System.out.println(s1.toString());System.out.println(s2.toString());} catch (CloneNotSupportedException e) {e.printStackTrace();}}
}
在修改s1
的name
时,s2
就不会随着s1
而变动了。
2.3 Object类中clone方法的定义和用途
Object 类中的 clone() 方法用于创建当前对象的一个副本(即对象的浅拷贝)。它是一个受保护的方法(用protected修饰),所以默认情况下只有 Object 类本身及其子类可以调用。在实际开发中,clone() 方法常常被重写,以便提供更灵活的复制功能。
clone
方法在object
类的声明如下:
protected Object clone() throws CloneNotSupportedException;
想要深入的了解Object和clone方法请点击:Java的生命之源:走进Object类的神秘花园,解密Object类的背后故事
说明:
- 返回值类型:Object 类型。返回的是当前对象的副本,因此返回值需要被强制转换为具体的类型。
- 异常:CloneNotSupportedException,如果对象的类没有实现 Cloneable 接口,调用 clone() 方法时会抛出此异常。
clone()
方法是 Object
类中定义的,用于返回一个与当前对象相同的副本。在默认实现中,它是浅拷贝,意味着它会复制对象的基本数据类型字段,但如果对象包含引用类型的字段,那么这些字段依然指向原来的对象。
3. 浅拷贝
浅拷贝指的是在复制对象时,只复制对象的基本数据类型字段和引用字段的引用(即内存地址),并没有复制引用字段所指向的对象本身。也就是说,原对象和拷贝对象会共享对引用类型字段(例如数组、集合等)的引用。这就导致了如果你修改原对象中的引用类型字段,拷贝对象中的相同字段也会被修改。
在上述的例子中,super.clone()
方法通过 Object
类的 clone()
方法创建一个新的 Student
对象。
String name = “***”:这种方式使用字符串字面量创建字符串。
String name = new String(“***”):这种方式会在堆内存中创建一个新的 String 对象,其内容为 “***”,当name的内容改变时,会再创建一个空间,然后把新的引用(空间地址)赋值给name。
由于 name(name=“***”) 和 age 都是基本数据类型和不可变对象(如 String),拷贝的过程中,name 和 age 会被直接复制。
对于 name 字段,String 是不可变的,所以即使修改原对象的 name 字段,拷贝对象的 name 字段也不会受到影响。
但是,当我们在Studnet
类中提添加一个自定义的引用类型,再次仿照上述Main()
类中的main
方法操作,发生什么呢?
class Person{public int number; // 电话号码public Person(int number){this.number = number;}
}class Student implements Cloneable{public String name;public int age;Person person;public Student(String name, int age,int number){person = new Person(number);this.name = name;this.age = age;}@Overridepublic Student clone() throws CloneNotSupportedException{return (Student)super.clone();}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", number=" + person.number +'}';}
}public class Main {public static void main(String[] args){try {// 创建一个对象Student s1 = new Student("李华",18,111);// 把 s1的值赋值给s2Student s2 = s1.clone();System.out.println(s1.toString());System.out.println(s2.toString());// 修改s1的号码为888s1.person.number = 888;System.out.println("只修改s1的号码为 888");System.out.println(s1.toString());System.out.println(s2.toString());} catch (CloneNotSupportedException e) {e.printStackTrace();}}
}
使用clone
后,s1
和s2
不是指向各自自己的空间了吗,为什么改变s1
中的值而s2
中的值也发生改变了呢?
这就是浅拷贝,浅拷贝指的是在复制对象时,只复制对象的基本数据类型字段和引用字段的引用(即内存地址),并没有复制引用字段所指向的对象本身
在Studnet
类中,用一个引用变量person
,创建s1
时person
指向自己的空间,person
存储的是引用(空间地址,比如是0X555
),当把s1
克隆给s2
时s2
中的person
存储也是0X555
为了解决这一问题,需要自己重写clone
方法,并且手动的给person
开空间。
4. 深拷贝
4.1 什么是深拷贝?
简单的来说,深拷贝就是解决浅拷贝存在的问题,为对象中的引用类型也开辟自己的空间,把类的赋值完美化。
4.2 解决浅拷贝遗留的问题
为了解决浅拷贝遗留的问题,需要在Person
类中也写一个clone
方法:
// 实现接口
class Person implements Cloneable{public int number; // 电话号码public Person(int number){this.number = number;}// 重写写clone方法public Person clone() throws CloneNotSupportedException{return (Person)super.clone(); // 调用clone方法}}
需要在Student
中改进clone
方法的定义,使用Person
中的clone方法:
public Student clone() throws CloneNotSupportedException{// 创建一个Studnet临时对象 tmp// 此时的s.person 和 this.person 存储的相同的引用Student tmp = (Student)super.clone();// 使用person的clone方法创建一个新的对象// s.person 指向的是新的对象tmp.person = person.clone();return tmp;}
改为深拷贝的整体代码:
class Person implements Cloneable{public int number; // 电话号码public Person(int number){this.number = number;}public Person clone() throws CloneNotSupportedException{return (Person)super.clone();}}class Student implements Cloneable{public String name;public int age;Person person;public Student(String name, int age,int number){person = new Person(number);this.name = name;this.age = age;}@Overridepublic Student clone() throws CloneNotSupportedException{// 创建一个Studnet临时对象 tmp// 此时的s.person 和 this.person 存储的相同的引用Student tmp = (Student)super.clone();// 使用person的clone方法创建一个新的对象// s.person 指向的是新的对象tmp.person = person.clone();return tmp;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", number=" + person.number +'}';}
}public class Main {public static void main(String[] args){try {// 创建一个对象Student s1 = new Student("李华",18,111);// 把 s1的值赋值给s2Student s2 = s1.clone();System.out.println(s1.toString());System.out.println(s2.toString());// 修改s1的号码为888s1.person.number = 888;System.out.println("只修改s1的号码为 888");System.out.println(s1.toString());System.out.println(s2.toString());} catch (CloneNotSupportedException e) {e.printStackTrace();}}
}
运行程序:
显然,当s1
改变时s2
不会改变,s1
中的引用类型person
和s2
中的person
指向不同的空间。
4.3 总结
关键点:
深拷贝的核心是递归地复制引用类型字段所指向的对象,而不仅仅是复制它们的引用。
浅拷贝会让原对象和拷贝对象共享引用类型字段,而 深拷贝 会确保原对象和拷贝对象的引用类型字段相互独立。
在上面的例子中,Student 类中的 person 字段是通过 clone() 方法进行深拷贝的,修改原对象的 person 字段不会影响拷贝对象。
深拷贝的应用:
独立性:深拷贝常用于确保原对象和拷贝对象在引用类型字段上相互独立,避免它们相互影响。
内存管理:在某些场景下,深拷贝可以避免对共享资源的修改导致意外的副作用。
总结:
深拷贝是对象复制中的一种高级技术,能够确保对象之间完全独立,特别适用于包含引用类型字段的复杂对象。当需要确保每个对象的字段都能被独立地复制,并且修改一个对象不会影响另一个对象时,深拷贝是必不可少。
相关文章:

【Java 学习】对象赋值的艺术:Java中clone方法的浅拷贝与深拷贝解析,教你如何在Java中实现完美复制
💬 欢迎讨论:如对文章内容有疑问或见解,欢迎在评论区留言,我需要您的帮助! 👍 点赞、收藏与分享:如果这篇文章对您有所帮助,请不吝点赞、收藏或分享,谢谢您的支持&#x…...

基于高斯混合模型的数据分析及其延伸应用(具体代码分析)
一、代码分析 (一)清除工作区和命令行窗口 clear; clc;clear;:该命令用于清除 MATLAB 工作区中的所有变量,确保代码运行环境的清洁,避免之前遗留的变量对当前代码运行产生干扰。例如,如果之前运行的代码中…...
无人机+Ai应用场景!
军事领域 无人机AI制导技术在军事领域的应用尤为突出。通过AI技术,无人机可以自主执行侦察、监视、打击等多种任务,极大地提高了军事行动的效率和准确性。 侦察与监视:AI无人机能够利用先进的传感器和摄像头,对目标区域进行大范…...

操作手册:集成钉钉审批实例消息监听配置
此文档将记录在慧集通平台怎么实现钉钉审批实例结束或发起或取消时,能够实时的将对应的实例数据抓取出来送入第三方系统 集成平台配置 1、配置中心库,存储钉钉发送的消息,可以忽略,若不配置,则钉钉的消息将不再记录到…...
AI大模型-提示工程学习笔记4
卷首语:我所知的是我自己非常无知,所以我要不断学习。 写给AI入行比较晚的小白们(比如我自己)看的,大神可以直接路过无视了。 不同主题提示词可以完成不同基本任务,常见的提示主题有: 文本概…...

Vue3.5 企业级管理系统实战(一):项目初始搭建与配置
本文详细介绍了如何使用 Vite 构建一个高效的 Vue 3.5 项目框架,并整合了 ESLint、Prettier、EditorConfig、Husky、lint-staged 和 commitlint 等现代化开发工具。通过这些工具的集成,我们能够确保代码质量、格式化和提交规范的一致性,从而提…...
缓存-Redis-缓存更新策略-主动更新策略-Cache Aside Pattern(全面 易理解)
**Cache-Aside Pattern(旁路缓存模式)**是一种广泛应用于缓存管理的设计模式,尤其在使用 Redis 作为缓存层时尤为常见。该模式通过在应用程序与缓存之间引入一个旁路,确保数据的一致性和高效性。本文将在之前讨论的 Redis 主动更新…...
杭州市有哪些大学能够出具论文检索报告?
杭州市具有查收查引服务的学校有浙江大学、杭州电子科技大学、浙江工业大学、杭州师范大学等高校。 1、浙江大学图书馆 浙江大学图书馆提供文献查收查引服务,包括查询学术论文被SCIE、SSCI、A&HCI、EI、CPCI-S、CPCI-SSH、CSSCI、CSCD等国内外权威数据库收录和…...

SpringBootWeb 登录认证(day12)
登录功能 基本信息 请求参数 参数格式:application/json 请求数据样例: 响应数据 参数格式:application/json 响应数据样例: Slf4j RestController public class LoginController {Autowiredpriva…...
使用AOP在切面逻辑中无法获取到requesetBody
使用场景:在接口处理之前,我们需要拿到请求参数,对参数进行校验。注意,这里需要拿到的是原始的请求信息! 一般的获取方式 ServletInputStream inputStream request.getInputStream(); StringBuilder stringBuilder …...

生成模型:变分自编码器-VAE
1.基本概念 1.1 概率 这里有: x为真实图像,开源为数据集, 编码器将其编码为分布参数 x ^ \hat{x} x^为生成图像, 通过解码器获得 p ( x ) ^ \hat{p(x)} p(x)^: 观测数据的分布, 即数据集所构成的经验分布 p r e a l ( x ) p_{real}(x) preal(x): …...
Hive sql执行文件合并配置参数
HIVE自动合并输出的小文件的主要优化手段为:HIVE将会启动一个独立的map-reduce任务进行输出文件的merge。 set hive.merge.mapfiles true: 在只有map的作业结束时合并小文件, set hive.merge.mapredfiles true: 在Map-Reduce的任…...

鸿蒙 ArkUI实现地图找房效果
常用的地图找房功能,是在地图上添加区域、商圈、房源等一些自定义 marker,然后配上自己应用的一些筛选逻辑构成,在这里使用鸿蒙 ArkUI 简单实现下怎么添加区域/商圈、房源等 Marker. 1、开启地图服务 在华为开发者官网,注册应用&…...

一套极简易的直流无刷电机(Deng FOC)开发套件介绍
目录 概述 1. 硬件组成介绍 1.1 主要硬件 1.2 电机驱动板介绍 1.3 2208电机模块 1.3.1 参数介绍 1.3.2 认识2208电机 2 驱动板接口介绍 2.1 PCB接口(MCU)定义 2.2 功能描述 2.2.1 电机驱动接口 2.2.2 编码器接口 2.2.3 电流输入引脚接口 2.…...
Inception模型详解及代码分析
模型背景 Inception系列模型由Google团队提出,旨在解决CNN分类模型面临的两大挑战: 如何在增加网络深度的同时提升分类性能 如何在保证分类准确率的同时降低计算和内存开销 Inception V1通过引入 并行卷积结构 和 1x1卷积 ,巧妙地解决了这两个问题,在保证模型质量的前提下…...
Springboot AOP 每个接口运行前 修改入参
控制台log输出为何频频失踪? wxss代码为何频频失效? wxml布局为何乱作一团? 究竟是道德的沦丧?还是人性的缺失? 让我们一起来 走 跑进科学 前言 麻蛋被这个功能恶心好久 终于解决了 特此记录一下 正文 Before("authCut()")public void cutProc…...

课题推荐——基于GPS的无人机自主着陆系统设计
关于“基于GPS的无人机自主着陆系统设计”的详细展开,包括项目背景、具体内容、实施步骤和创新点。如需帮助,或有导航、定位滤波相关的代码定制需求,请点击文末卡片联系作者 文章目录 项目背景具体内容实施步骤相关例程MATLAB例程python例程 …...
【深度学习】在深度学习训练过程中,数据量太少会导致模型过拟合还是欠拟合?
过拟合与欠拟合 过拟合 : 是指在训练集上表现非常好,但是在新的数据集上表现较差的现象。具体来说,模型在训练集上过度学习,捕捉了数据中的噪声和偶然性,导致它对训练数据的拟合非常精确,但缺乏泛化能力,无…...

js迭代器模式
以前JS原生的集合类型数据结构,只有Array(数组)和Object(对象); 而ES6中,又新增了Map和Set。四种数据结构各自有着自己特别的内部实现,但我们仍期待以同样的一套规则去遍历它们&…...
测试开发基础知识2
10.什么是等价类和边界值法? 1)等价类划分 等价类划分是将系统的输入域划分为若干部分,然后从每个部分选取少量代表性数据进行测试。等价类划分认为如果一个测试用例在某个等价类中的一个值上通过测试,那么它在这个类中的其他值上也…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...

ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...

嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...

如何做好一份技术文档?从规划到实践的完整指南
如何做好一份技术文档?从规划到实践的完整指南 🌟 嗨,我是IRpickstars! 🌌 总有一行代码,能点亮万千星辰。 🔍 在技术的宇宙中,我愿做永不停歇的探索者。 ✨ 用代码丈量世界&…...

Python异步编程:深入理解协程的原理与实践指南
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 持续学习,不断…...
AT模式下的全局锁冲突如何解决?
一、全局锁冲突解决方案 1. 业务层重试机制(推荐方案) Service public class OrderService {GlobalTransactionalRetryable(maxAttempts 3, backoff Backoff(delay 100))public void createOrder(OrderDTO order) {// 库存扣减(自动加全…...

Spring AI中使用ChatMemory实现会话记忆功能
文章目录 1、需求2、ChatMemory中消息的存储位置3、实现步骤1、引入依赖2、配置Spring AI3、配置chatmemory4、java层传递conversaionId 4、验证5、完整代码6、参考文档 1、需求 我们知道大型语言模型 (LLM) 是无状态的,这就意味着他们不会保…...
git删除本地分支和远程分支
删除本地分支 git branch -d 分支名删除远程分支 git push origin --delete 分支名...