Java 中的深拷贝和浅拷贝你了解吗?

news/2024/11/14 12:32:26/文章来源:https://www.cnblogs.com/fuxing/p/18203554

前言

Java 开发中,对象拷贝是常有的事,很多人可能搞不清到底是拷贝了引用还是拷贝了对象。本文将详细介绍相关知识,让你充分理解 Java 拷贝。


一、对象是如何存储的?

方法执行过程中,方法体中的数据类型主要分两种,它们的存储方式是不同的(如下图):

  1. 基本数据类型: 直接存储在栈帧的局部变量表中;
  2. 引用数据类型: 对象的引用存储在栈帧的局部变量表中,而对实例本身及其所有成员变量存放在堆内存中。

详情可见JVM基础

image.png

二、前置准备

创建两个实体类方便后续的代码示例

@Data
@AllArgsConstructor
public class Animal{private int id;private String type;@Overridepublic String toString () {return "Animal{" +"id=" + id +", type='" + type + '\'' +'}';}
}
@Data
@AllArgsConstructor
public class Dog {private int age;private String name;private Animal animal;@Overridepublic String toString () {return "Dog{" +"age=" + age +", name='" + name + '\'' +", animal=" + animal +'}';}
}

三、直接赋值

直接赋值是我们最常用的方式,它只是拷贝了对象引用地址,并没有在内存中生成新的对象

下面我们进行代码验证:

public class FuXing {public static void main (String[] args) {Animal animal = new Animal(1, "dog");Dog dog = new Dog(18, "husky", animal);Dog dog2 = dog;System.out.println("两个对象是否相等:" + (dog2 == dog));System.out.println("----------------------------");dog.setAge(3);System.out.println("变化后两个对象是否相等:" + (dog2 == dog));}
}
两个对象是否相等:true
----------------------------
变化后两个对象是否相等:true

通过运行结果可知,dog类的age已经发生变化,但重新打印两个类依然相等。所以它只是拷贝了对象引用地址,并没有在内存中生成新的对象

直接赋值的 JVM 的内存结构大致如下:

image.png

四、浅拷贝

浅拷贝后会创建一个新的对象,且新对象的属性和原对象相同。但是,拷贝时针对原对象的属性的数据类型的不同,有两种不同的情况:

  1. 属性的数据类型基本类型,拷贝的就是基本类型的值;
  2. 属性的数据类型引用类型,拷贝的就是对象的引用地址,意思就是拷贝对象与原对象引用同一个对象

要实现对象浅拷贝还是比较简单的,只需要被拷贝的类实现Cloneable接口,重写clone方法即可。下面我们对Dog进行改动:

@Data
@AllArgsConstructor
public class Dog implements Cloneable{private int age;private String name;private Animal animal;@Overridepublic Dog clone () throws CloneNotSupportedException {return (Dog) super.clone();}@Overridepublic String toString () {return "Dog{" +"age=" + age +", name='" + name + '\'' +", animal=" + animal +'}';}
}

接下来我们运行下面的代码,看一下运行结果:

public class FuXing {public static void main (String[] args) throws Exception {Animal animal = new Animal(1, "dog");Dog dog = new Dog(18, "husky", animal);// 克隆对象Dog cloneDog = dog.clone();System.out.println("dog:" + dog);System.out.println("cloneDog:" + cloneDog);System.out.println("两个对象是否相等:" + (cloneDog == dog));System.out.println("两个name是否相等:" + (cloneDog.getName() == dog.getName()));System.out.println("两个animal是否相等:" + (cloneDog.getAnimal() == dog.getAnimal()));System.out.println("----------------------------------------");// 更改原对象的属性值dog.setAge(3);dog.setName("corgi");dog.getAnimal().setId(2);System.out.println("dog:" + dog);System.out.println("cloneDog:" + cloneDog);System.out.println("两个对象是否相等:" + (cloneDog == dog));System.out.println("两个name是否相等:" + (cloneDog.getName() == dog.getName()));System.out.println("两个animal是否相等:" + (cloneDog.getAnimal() == dog.getAnimal()));}
dog:Dog{age=18, name='husky', animal=Animal{id=1, type='dog'}}
cloneDog:Dog{age=18, name='husky', animal=Animal{id=1, type='dog'}}
两个对象是否相等:false
两个name是否相等:true
两个animal是否相等:true
----------------------------------------
dog:Dog{age=3, name='corgi', animal=Animal{id=2, type='dog'}}
cloneDog:Dog{age=18, name='husky', animal=Animal{id=2, type='dog'}}
两个对象是否相等:false
两个name是否相等:false
两个animal是否相等:true

我们分析下运行结果,重点看一下 “两个name是否相等”,改动后变成 false.

这是因为StringInteger等包装类都是不可变的对象,当需要修改不可变对象的值时,需要在内存中生成一个新的对象来存放新的值,然后将原来的引用指向新的地址

这里dog对象的name属性已经指向一个新的对象,而cloneDogname属性仍然指向原来的对象,所以就不同了。

然后我们看下两个对象的animal属性,原对象属性值变动后,拷贝对象也跟着变动,这就是因为拷贝对象与原对象引用同一个对象

浅拷贝的 JVM 的内存结构大致如下:

image.png

五、深拷贝

与浅拷贝不同之处,深拷贝在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且拷贝其成员变量。也就是说,深拷贝出来的对象,与原对象没有任何关联,是一个新的对象。

实现深拷贝有两种方式

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

注意: 这里如果引用类型的属性或者层数太多了,代码量会变很大,所以一般不建议使用

@Data
@AllArgsConstructor
public class Animal implements Cloneable{private int id;private String type;@Overrideprotected Animal clone () throws CloneNotSupportedException {return (Animal) super.clone();}@Overridepublic String toString () {return "Animal{" +"id=" + id +", type='" + type + '\'' +'}';}
}
@Data
@AllArgsConstructor
public class Dog implements Cloneable{private int age;private String name;private Animal animal;@Overridepublic Dog clone () throws CloneNotSupportedException {Dog clone = (Dog) super.clone();clone.animal = animal.clone();return clone;}@Overridepublic String toString () {return "Dog{" +"age=" + age +", name='" + name + '\'' +", animal=" + animal +'}';}
}

我们再次运行浅拷贝部分的main方法,结果如下。

dog:Dog{age=18, name='husky', animal=Animal{id=1, type='dog'}}
cloneDog:Dog{age=18, name='husky', animal=Animal{id=1, type='dog'}}
两个对象是否相等:false
两个name是否相等:true
两个animal是否相等:false # 变为false
----------------------------------------
dog:Dog{age=3, name='corgi', animal=Animal{id=2, type='dog'}}
cloneDog:Dog{age=18, name='husky', animal=Animal{id=1, type='dog'}}
两个对象是否相等:false
两个name是否相等:false
两个animal是否相等:false # 变为false

2.序列化

序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取出来。我们可以利用对象的序列化产生克隆对象,然后通过反序列化获取这个对象。

@Data
@AllArgsConstructor
public class Animal implements Serializable {private int id;private String type;@Overridepublic String toString () {return "Animal{" +"id=" + id +", type='" + type + '\'' +'}';}
}
@Data
@AllArgsConstructor
public class Dog implements Serializable {private int age;private String name;private Animal animal;@SneakyThrows@Overridepublic Dog clone () {// 序列化ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);//反序列化ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);return (Dog) ois.readObject();}@Overridepublic String toString () {return "Dog{" +"age=" + age +", name='" + name + '\'' +", animal=" + animal +'}';}
}

我们再次运行浅拷贝部分的main方法,结果如下。

dog:Dog{age=18, name='husky', animal=Animal{id=1, type='dog'}}
cloneDog:Dog{age=18, name='husky', animal=Animal{id=1, type='dog'}}
两个对象是否相等:false
两个name是否相等:false # 变为false
两个animal是否相等:false # 变为false
----------------------------------------
dog:Dog{age=3, name='corgi', animal=Animal{id=2, type='dog'}}
cloneDog:Dog{age=18, name='husky', animal=Animal{id=1, type='dog'}}
两个对象是否相等:false
两个name是否相等:false
两个animal是否相等:false # 变为false

深拷贝的 JVM 的内存结构大致如下:

image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/709939.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

“垃圾佬”来了

出任CEO,迎娶白富美,走向人生巅峰.的第一步先买个服务器 一个带显卡的服务器Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` “垃圾佬”来了 日期:2020-8-18 阿珏 谈天说地 浏览:…

pycharm下面的terminal终端报错,出问题!

转自某位大佬,原文:https://blog.csdn.net/qq_57892905/article/details/131966626报错是这样的 但是cmd运行该命令不报错这是大佬的解决办法,我运行成功了解决办法: 1:点击file(文件)2:点击Settings(设置) 3:接下来点击Tool(工具) 4:接下来点击Tool里面的termi…

火焰杯测试开发大赛赛前辅导——web自动化

为了帮助各位同学更好地准备比赛并取得优异成绩,测吧将为本次比赛提供竞赛平台技术支持和赛事运营服务,同时免费提供赛前辅导学习资料以及相关技术交流答疑服务。本周赛前辅导内容为Web自动化测试。 **直播内容 **环境搭建 学习路径 控件定位与交互 自动化测试用例编写 自动…

2020端午团队旅游

终于有时间出来发博客了。这个端午没浪费,我们团队一行人去往了惠州市双月湾度过了2晚3日的愉快之旅Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` 2020端午团队旅游 日期:2020-7-5…

TB1801 低压差单路线性 LED 恒流芯片与车灯应用

1、产品概述TB1801 是一款专为 12V 灯珠设计的汽车灯专用的低压差恒流芯片,输出电流恒流精度≤3%,外围结构简单。TB1801 内置 130℃过温保护电路,可在各种散热条件下将 LED 灯珠温度控制在 140℃以内。TB1801 内置 200mΩ/100V 的功率 MOS,最大应用电流 2A;耐压100V;采用…

JavaScript execute asynchronous functions in Parallel with count and Promise All In One

JavaScript execute asynchronous functions in Parallel with count and Promise All In One JavaScript 使用 count 和 Promise 并行执行异步函数 LeetCode 2721. Execute Asynchronous Functions in ParallelJavaScript execute asynchronous functions in Parallel with co…

Android查看apk安装包的AndroidManifest.xml文件

上周在做安卓系统的厂家推送功能,某些机型无法离线厂推,所以准备解包apk看一下打包参数是否出问题。Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` Android查看apk安装包的AndroidM…

pip 安装报错 SSLCertVerificationError

报错原因是开启了代理 关闭代理服务之后,命令即可重新运行

如何利用ITSM工具带好一只运维团队

在信息技术日新月异的今天,IT运维团队面临着前所未有的挑战与机遇。随着ITIL4框架的引入和企业信息中心的不断升级,传统的“大锅饭”式管理方式已难以适应快速变化的业务需求和技术环境。面对人心可能的涣散、核心人才的流失以及团队动力的缺失,如何运用现代IT服务管理(ITS…

mysql-8.4.0解压版安装记录

MySQL 8.4.0解压版安装记录 这几天,安装最新版mysql 8.4的时候,遇到了不少问题,网上的教程大多数都是旧版本的,也安装不成功。 参考了大量教程后,经过自己的摸索终于装好了,这里记录一下。 我下载的是8.4.0 LTS MySQL :: Download MySQL Community Server下载后解压,放…

npm 或 yarn安装依赖报错 EPERM: operation not permitted, unlink 解决方法

1. 权限问题检查是否限制为只有管理员才能操作,打开当前项目的文件夹然后选中要打开的项目文件,在点击左上角的 文件 然后以管理员身份运行。在里面执行安装命令npm i ...,yarn add .... 2. 缓存问题npm清理缓存命令:npm cache clean --forceyarn清理缓存命令: yarn cach…

DELL 主板11针LEDH1接线方法

如图:接口解释:这种接口是开机、重启一个按键 设计的,接线: 重启有点麻烦。作者:柒月出处:https://www.cnblogs.com/qiynet/开源:https://github.com/qiy/站点:https://qiy.net/Q群 :2122210(嵌入式/机器学习)