Java深拷贝与浅拷贝

Java深拷贝与浅拷贝

    • 一、深拷贝与浅拷贝
    • 二、实现方式
      • 1、使用Object中的clone方法
      • 2、通过对象序列化 (实现Serializable接口)
    • 三、原型设计模式

一、深拷贝与浅拷贝

  • 浅拷贝只会复制对象中基本数据类型数据和引用数据的对象内存地址,不会递归的复制引用对象、以及引用对象的引用对象…即浅拷贝得到的对象跟原始对象共享数据。
  • 深拷贝不仅会复制索引,还会复制数据本身,得到的是一份完完全全的独立对象。所以深拷贝比浅拷贝来说更加厚实,更加耗内存空间。

二、实现方式

1、使用Object中的clone方法

使用Object中的clone方法,必须实现Cloneable接口才可以调用clone()方法,否则抛出CloneNotSupportedException异常,深拷贝也需要实现Cloneable接口,同时其成员变量为引用类型的也需要实现Cloneable接口,然后重写clone方法。Cloneable接口是一个空接口,记作他是一个标记接口,实现Cloneable接口的类被标记为可以被clone的类。

代码示例:

import java.util.ArrayList;
import java.util.List;/*** @Author Long* @Date 2023/9/9 11:32*/
public class People implements Cloneable {private String name;private String age;private Info info; // 是一个对象,也必须继续拷贝/*** 深拷贝** @return Info*/public People deepClone() {People people;try {people = (People) super.clone();people.info = this.info.deepClone();} catch (CloneNotSupportedException exception) {people = null;System.out.println("clone Info error: " + exception);}return people;}}class Info implements Cloneable {private String address;private List<String> familyMember = new ArrayList<>();/*** 深拷贝** @return Info*/public Info deepClone() {Info info;try {info = (Info) super.clone();info.familyMember = new ArrayList<>();info.familyMember.addAll(this.familyMember);} catch (CloneNotSupportedException exception) {info = null;System.out.println("clone Info error: " + exception);}return info;}
}

如上述代码示例,若People的属性不止三个,这时如果想得到一份数据一样但是完全独立的对象,则可以使用深拷贝的方式,相当于复制对象。且People中存在Info引用对象,则Info也得继续实现拷贝,才是真正实现的深拷贝,否则复制出来的Info是指向同一个内存对象,即为浅拷贝。

2、通过对象序列化 (实现Serializable接口)

使用序列化的方式来复制对象 对象需要继承Serializable接口。先将对象序列化,然后再反序列化成新的对象。
Serializable 接口是一个标记接口,不用实现任何方法,仅用于标识可序列化的语义。
反序列化实现 Serilaziable 接口的类并不会调用构造方法。反序列的对象是由 JVM(java虚拟机) 以存储的二进制位为基础来构造,不通过构造方法生成。

  • Serialization(序列化):将 java 对象以一连串的字节序列保存在本地磁盘中的过程,也可以说是保存 java 对象状态的过程。序列化可以将数据永久保存在磁盘上(通常保存在文件中),避免程序运行结束后对象从内存中消失,字节序列也方便在网络中传输。
  • Deserialization(反序列化):将保存在磁盘文件中的 java 字节序列重新转换成 java 对象称为反序列化。

对象是序列化的作用:

  • 1)对象持久化: 把对象的字节序列保存在磁盘或数据库中,一般是存在文件里,这使得对象的状态可以在程序重启或跨网络传输后得以恢复,方便数据的存储和传输。
  • 2)网络传输对象:在分布式系统中,对象的序列化与反序列化可以实现对象在网络中的传输,将对象转换成字节流后,可以通过网络将字节流发送给其他节点,接收方再将字节流反序列化为对象进行处理。
  • 3)共享对象状态: 在多线程或分布式环境中,对象的状态共享。可能引发并发访问问题,通过将对象序列化,可以将对象的状态进行共享,从而实现对象状态的一致性和同步。

序列化步骤:
步骤一:创建一个 ObjectOutputStream 输出流;
步骤二:调用 ObjectOutputStream 对象的 writeObject() 输出可序列化对象。

反序列化步骤:
步骤一:创建一个 ObjectInputStream 输入流;
步骤二:调用 ObjectInputStream 对象的 readObject() 得到序列化的对象。

代码示例:

import java.io.Serializable;
public class User implements Serializable {private String name;private Address2 address;public User(String name, Address2 address) {this.name = name;this.address = address;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Address2 getAddress() {return address;}public void setAddress(Address2 address) {this.address = address;}/*** 深拷贝** @return Object*/public User deepClone() throws Exception{// 序列化ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);// 反序列化ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);return (User) ois.readObject();}
}class Address2 implements Serializable {private String city;private String country;public Address2(String city, String country) {this.city = city;this.country = country;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}public String getCountry() {return country;}public void setCountry(String country) {this.country = country;}@Overridepublic String toString() {return "Address2{" +"city='" + city + '\'' +", country='" + country + '\'' +'}';}
}
class Test {public static void main(String[] args) {try {// 创建一个 ObjectOutputStream 输出流ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\person.txt"));// 第一次序列化 person 对象Person person = new Person("涛涛", 21);oos.writeObject(person);// 第二次序列化 personoos.writeObject(person);// 创建一个 ObjectInputStream 输入流ObjectInputStream ios = new ObjectInputStream(new FileInputStream("D:\\person.txt"));// 依次反序列化出 p1、p2Person p1 = (Person) ios.readObject();Person p2 = (Person) ios.readObject();// 判断 p1、p2 是否是同一对象int i1 = System.identityHashCode(p1);int i2 = System.identityHashCode(p2);System.out.println("i1 = " + i1);System.out.println("i2 = " + i2);} catch (Exception e) {e.printStackTrace();}}
}>>> 输出结果:
i1 = 1349393271
i2 = 1349393271

serializable 接口的注意事项:

  • 如果实现 Serializable 接口的类有父类,则父类也必须可以序列化,若父类没有实现序列化接口,则父类必须有无参构造函数,否则会抛异常 java.io.InvalidClassException。因为在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。
  • 序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量。即static属性不会被序列化
  • 某些属性不需要序列化。使用 transient 关键字选择不需要序列化的字段。
  • 同一对象多次序列化,并不会得到多个二进制流,既不会反序列化为多个对象。而是只有第一次序列化为二进制流,以后都只是保存序列化版本号,且按自上而下的顺序依次保存。(反序列化时的顺序与序列化时的顺序一致)
  • java 序列化提供了一个 “private static final long serialVersionUID” 的序列化版本号,只要版本号相同,即使更改了序列化属性,对象也可以正确被反序列化回来。所以序列化一个类时最好指定一个序列化版本号,方便项目升级。JVM 首先会通过类名来区分 Java 类,类名不同,则不是同一个类。当类名相同时,JVM 就会通过序列化版本号来区分 Java 类,如果序列化版本号相同就为同一个类,序列化版本号不同就为不同的类。在序列化一个对象时,如果没有指定序列化版本号,后期对该类的源码进行修改并重新编译后,可能会导致修改前后的序列化版本号不一致,因为 JVM 会提供一个新的序列化版本号给该类对象,此时再用以往的反序列化代码去反序列化该类的对象,会导致反序列化使用的class的版本号与序列化时使用的不一致,就会抛出异常 java.io.InvalidClassException。
  • 在Java中,如果一个实现了Serializable接口的类没有指定serialVersionUID,那么JVM在序列化时,会根据属性自动生成一个serialVersionUID,然后和属性一起序列化,再进行网络传输或者持久化。在反序列化时,JVM会再根据属性生成一个新版本的serialVersionUID,然后再用这个新版本和serialVersionUID和序列化时生成的旧版本的serialVersionUID进行计较。如果二者一样就可以序列化成功。反之,报错。
  • 如果我们显示指定了serialVersionUID,JVM在序列化和反序列化时,就会使用我们指定的serialVersionUID。这样我们就可以确保在反序列化时,serialVersionUID和之前的相同。

三、原型设计模式

深拷贝与浅拷贝就是原型设计模式的两种实现方式。

如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同,在这种情况下可以利用对已有对象(原型)进行复制(拷贝)的方式来创建新对象,以达到节省创建时间的目的,这种基于原型来创建对象的方式就叫做原形设计模式,简称原形模式。

实际上创建对象包含的申请内存、给成员变量赋值这一过程本身并不会花费太多时间,或者说对于大部分业务系统来说,这些时间完全是可以忽略的,应用一个复杂的模型只得到一点点的性能提升,就是所谓的过渡设计、得不偿失。
但是如果对象中的数据需要经过复杂的计算才能得到,(比如排序、计算哈希值),或者需要从PC网络,数据库,文件系统等非常慢速的io中读取,这种情况下就可以使用原型模式从其他已有对象中直接拷贝得到,而不用每次在创建新对象的时候都重复执行这些耗时的操作。

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

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

相关文章

[NOIP2005 普及组] 采药

题目描述 辰辰是个天资聪颖的孩子&#xff0c;他的梦想是成为世界上最伟大的医师。为此&#xff0c;他想拜附近最有威望的医师为师。医师为了判断他的资质&#xff0c;给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说&#xff1a;“孩子&#xff0c;这个山洞里…

containerd使用了解

containerd使用了解 yum安装 [rootvm ~]# curl -o /etc/yum.repos.d/docker.repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo [rootvm ~]# yum list | grep containerd containerd.io.x86_64 1.6.28-3.1.el7 doc…

2_2.Linux中的远程登录服务

# 一.Openssh的功能 # 1.sshd服务的用途# #作用&#xff1a;可以实现通过网络在远程主机中开启安全shell的操作 Secure SHell >ssh ##客户端 Secure SHell daemon >sshd ##服务端 2.安装包# openssh-server 3.主配置文件# /etc/ssh/sshd_conf 4.…

list(链表)容器(二)

一、list 插入和删除 函数原型&#xff1a; push_back(elem);//在容器尾部加入一个元素 pop_back();//删除容器中最后一个元素 push_front(elem);//在容器开头插入一个元素 pop_front();//从容器开头移除第一个元素 insert(pos,elem);//在pos位置插elem元素的拷贝&#xff0c…

Rhino.Inside.Revit根据直线创建剖面视图

Hello大家好&#xff01;我是九哥~ 今天简单分享一个小节点&#xff0c;根据Revit线构件的Location Line创建Revit剖面视图&#xff0c;主要用于快速创建线管、风道、墙等构件的详图视图。 效果如下&#xff1a; Rhino.Inside.Revit根据直线创建剖面视图 这次这个节点&#xff…

python中raise_for_status方法的作用

文章目录 说明示例1:基本使用示例2:多种异常说明 raise_for_status() 方法在 Python 的 requests 库中用于在发送 HTTP 请求后检查响应的状态码。如果响应的状态码表示请求未成功(即状态码不是 2xx),则该方法会抛出一个 HTTPError 异常。这允许你以一种更结构化的方式来处…

【C语言】字符串

C语言用字符数组存放字符串&#xff0c;字符数组中的各元素依次存放字符串的各字符 一维字符数组&#xff1a;存放一个字符串&#xff08;每个数组元素存放一个字符&#xff09;二维字符数组&#xff1a;存放多个一维数组&#xff08;字符串&#xff09;&#xff1b;二维数组的…

Python - 深度学习系列31 - ollama的搭建与使用

说明 做这个的主要目的是为了搭建Langchain的本地环境&#xff0c;使用LangChain让LLM具备调用自定义函数的功能。 内容 1 安装server 以下将ollama的安装方式&#xff0c;以及使用做一个简单的说明(记录&#xff09;。之前对这个工具没有了解&#xff0c;只是从快速实践的…

Linux速览(2)——环境基础开发工具篇(其一)

本章我们来介绍一些linux的常用工具 目录 一. Linux 软件包管理器 yum 1.什么是软件包? 2. 查看软件包 3. 如何安装软件 4. 如何卸载软件 5.yum补充 6. 关于 rzsz 二. Linux编辑器-vim使用 1. vim的基本概念 2. vim的基本操作 3. vim正常模式命令集 4. vim末行模式…

2013年认证杯SPSSPRO杯数学建模C题(第一阶段)公路运输业对于国内生产总值的影响分析全过程文档及程序

2013年认证杯SPSSPRO杯数学建模 C题 公路运输业对于国内生产总值的影响分析 原题再现&#xff1a; 交通运输作为国民经济的载体&#xff0c;沟通生产和消费&#xff0c;在经济发展中扮演着极其重要的角色。纵观几百年来交通运输与经济发展的相互关系&#xff0c;生产水平越高…

AUTOSAR-OS上层需求总结

本文是《Requirements on Operating System》这篇官方文档的学习笔记&#xff0c;主要记录了一些AUTOSAR对操作系统(OS)相关的需求准则。 1 RTOS相关要求&#xff08;Real-Time Operating System&#xff09; 1.1 [SRS_Os_00097] 操作系统&#xff08;OS&#xff09;的API需要…

深度解析:国内主流音视频产品的核心功能与市场表现

前言 当前音视频开发领域呈现出多样化竞争态势&#xff0c;其中声网&#xff08;Agora&#xff09;、即构&#xff08;ZEGO&#xff09;等云通讯企业占据了市场的主导地位。随着技术的持续进步和用户需求的日益多样化&#xff0c;选择音视频服务提供商的标准也越来越个性化&am…

unity学习(74)——服务器Dispose异常

1.返回的1 2 11是怪物初始化&#xff0c;源代码中也没有 2. 3.客户端中的网络连接初始化如下&#xff1a; 4.不是因为超时&#xff0c;设置10s为超时期限后&#xff0c;客户端和服务器有时依然会报错&#xff01; 5.我感觉就是update中发包给弄坏的&#xff01; 6.不在“帧”…

Python版【植物大战僵尸 +源码】

文章目录 写在前面&#xff1a;功能实现环境要求怎么玩个性化定义项目演示&#xff1a;源码分享Map地图:Menubar.py主菜单 主函数&#xff1a;项目开源地址 写在前面&#xff1a; 今天给大家推荐一个Gtihub开源项目&#xff1a;PythonPlantsVsZombies&#xff0c;翻译成中就是…

【云开发笔记No.18】说说腾讯的TKE

在云原生开发领域中&#xff0c;TKE&#xff08;Tencent Kubernetes Engine&#xff09;是一个重要的组成部分。TKE是腾讯云提供的一种托管的容器管理服务&#xff0c;它基于开源的Kubernetes系统&#xff0c;旨在简化容器的部署、管理和扩展&#xff0c;同时提供高性能、可靠性…

web 技术中前端和后端交互过程

1、客户端服务器交互过程 客户端:上网过程中,负责浏览资源的电脑,叫客户端服务器:在因特网中,负责存放和对外提供资源的电脑叫服务器 服务器的本质: 就是一台电脑,只不过相比个人电脑它的性能高很多,个人电脑中可以通过安装浏览器的形式,访问服务器对外提供的各种资源。 个人…

【JavaEE初阶系列】——常见的锁策略

目录 &#x1f6a9;乐观锁和悲观锁 &#x1f6a9;读写锁和普通互斥锁 &#x1f6a9;轻量级锁和重量级锁 &#x1f6a9;自旋锁和挂起等待锁 &#x1f6a9;公平锁和非公平锁 &#x1f6a9;可重入锁和不可重入锁 &#x1f6a9;关于synchronized的锁策略以及自适应 接下来讲解的锁策…

transformers微调模型后使用pieline调用无法预测列表文本

初学transformers框架 使用trainer简单训练一个文本分类模型三个epoch后 使用piepline调用model 和tokenizer后 发现 传入列表文本后 输出就变得不正常了&#xff0c;为么子哇 如下图

git 代码管理仓库/安装部署

系统环境准备 cat /etc/reshat-release #查看系统版本 uname -r #查看版本内核 sed -i s/SELINUXenforcing/SELINUXdisabled/ /etc/selinux/config #关闭selinux systemctl stop firewalld #关闭防火墙 Git安装部署 rpm -qa | grep git …

macbook pro 2018 T2 芯片安装 archlinux 双系统

文章目录 [toc]配置无线网和分区修改时区安装内核和驱动自动生成 fstab 文件配置 pacman切换到安装好的系统设置时区配置 pacman 国内源安装 gui安装 GRUB 引导建立引导 增加内核模块配置开机自启修改 root 用户密码创建普通用户配置 sudo 重启进入新系统配置 wlan 自动连接 wi…