揭开Java序列化的神秘面纱(上)Serializable使用详解


Java序列化(Serialization)作为一项核心技术,在Java应用程序的多个领域都有着广泛的应用。无论是通过网络进行对象传输,还是实现对象的持久化存储,序列化都扮演着关键的角色。然而,这个看似简单的概念蕴含着丰富的原理和用法细节,值得我们一探究竟。


一、序列化的本质


序列化的本质是将对象转换为字节流,以便在网络上传输或存储在磁盘上。经过序列化后,虚拟机中的对象便可以脱离运行环境,转化为平台无关的原始字节流,方便进行传输和持久化。

Serializable序列化的本质可以从以下几个方面来理解:

  1. 对象状态的保存:序列化允许将对象的状态(即对象的字段值)保存到一个持久化存储中,例如文件或数据库。这意味着即使程序停止运行,对象的状态也可以被保存下来,并在需要时重新加载。
  2. 对象的传输:在分布式系统中,对象序列化可以用于通过网络传输对象。通过网络发送对象的序列化形式,然后在接收端进行反序列化,从而恢复对象的状态。
  3. 跨平台:序列化允许Java对象在不同的平台和Java虚拟机之间传输。只要序列化和反序列化的过程遵循相同的协议,对象就可以在不同的系统上保持一致。
  4. 兼容性:Java的序列化机制保证了对象的兼容性。即使类的实现发生了变化,只要类的序列化版本号(serialVersionUID)保持不变,旧的序列化对象仍然可以在新的类实现上进行反序列化。
  5. 简单性:实现序列化非常简单。开发者只需要声明类实现了Serializable接口,然后Java运行时环境就会自动处理对象的序列化和反序列化。
  6. 性能考虑:序列化和反序列化的过程可能会涉及到大量的I/O操作,这可能会影响程序的性能。因此,对于性能敏感的应用,需要仔细考虑序列化的使用。
  7. 安全性:序列化可能会带来安全风险,因为恶意的序列化数据可以被用来执行攻击。因此,需要对序列化的数据进行验证,确保它们是安全和可信的。
  8. 自定义序列化:Java允许开发者通过实现ObjectOutputStreamObjectInputStream的自定义版本来控制序列化和反序列化的过程。这可以用于优化性能或添加额外的序列化逻辑。
  9. 非静态字段:只有对象的非静态字段会被序列化。静态字段和方法不会被包含在序列化的数据中。
  10. 序列化代理:Java提供了一种机制,称为序列化代理(writeReplacereadResolve方法),允许开发者控制序列化和反序列化过程中对象的替换。


    序列化是Java中一个强大的特性,它使得对象可以在不同的环境和平台之间移动,同时也为对象的持久化提供了支持。然而,在使用序列化时也需要考虑到性能、安全性和兼容性等问题。

二、实现Serializable接口


在Java中,实现Serializable接口非常简单,因为这是一个标记接口,它不包含任何方法。要使一个类可序列化,你只需要声明它实现了Serializable接口。

下面是一个简单的Java类实现Serializable接口的示例:

import java.io.Serializable;public class Person implements Serializable {// 定义一个序列化版本号,建议添加,以确保序列化兼容性private static final long serialVersionUID = 1L;// 可以添加一些字段,这些字段将会被序列化private String name;private int age;private transient String password; // transient关键字表示该字段不会被序列化// 构造函数public Person(String name, int age, String password) {this.name = name;this.age = age;this.password = password;}// getter和setter方法public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}// 重写toString方法,方便打印对象信息@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +", password='" + password + '\'' +'}';}// 可以添加其他业务逻辑代码
}

在上面的代码中,`Person`类实现了`Serializable`接口,这意味着`Person`对象可以被序列化和反序列化。

serialVersionUID是一个可选的静态字段,用于在反序列化过程中确保发送方和接收方的序列化版本兼容。如果类实现Serializable接口,建议添加这个字段。

transient关键字用于指示某些字段在序列化过程中应该被忽略。在上面的例子中,password字段被标记为transient,这意味着在序列化Person对象时,密码字段不会被包含在序列化的数据中。

这个类还包含了一些基本的getter和setter方法,以及一个重写的toString方法,用于提供类的字符串表示,这在调试和日志记录时非常有用。


三、序列化和反序列化的流程


序列化的核心是通过ObjectOutputStream和ObjectInputStream来完成。

序列化过程:

  1. 创建一个ObjectOutputStream
  2. 调用writeObject方法输出可序列化对象

反序列化过程:

  1. 创建一个ObjectInputStream
  2. 调用readObject方法从流中读取字节,反序列化为对象

下面是一个简单的示例,演示如何序列化和反序列化Person对象:

import java.io.*;public class SerializationDemo {public static void main(String[] args) {try {// 创建Person对象Person person = new Person("John Doe", 30, "securepassword123");// 序列化FileOutputStream fileOut = new FileOutputStream("person.ser");ObjectOutputStream out = new ObjectOutputStream(fileOut);out.writeObject(person);out.close();fileOut.close();System.out.println("Serialized data is saved in 'person.ser'");// 反序列化FileInputStream fileIn = new FileInputStream("person.ser");ObjectInputStream in = new ObjectInputStream(fileIn);Person deserializedPerson = (Person) in.readObject();in.close();fileIn.close();System.out.println("Deserialized Person: " + deserializedPerson);} catch (IOException i) {i.printStackTrace();} catch (ClassNotFoundException c) {System.out.println("Person class not found");c.printStackTrace();}}
}

这段代码首先创建了一个`Person`对象,然后使用`ObjectOutputStream`将其序列化到一个文件中。之后,使用`ObjectInputStream`从文件中反序列化对象,并打印出来。注意,反序列化时需要处理`ClassNotFoundException`,这可能发生在找不到要反序列化的类定义时。

四、transient关键字


transient关键字在Java中用于控制序列化行为。当一个类实现了Serializable接口,所有的非静态字段(即实例字段)默认都会被序列化。然而,有些字段可能不适合被序列化,或者序列化这些字段没有意义,这时就可以使用transient关键字来标记这些字段。

1、transient关键字详解:

  1. 字段不序列化:被transient关键字标记的字段在对象序列化时不会被保存到序列化的数据中。这意味着在反序列化时,这些字段将保持默认值(例如,null对于对象引用,0对于整数类型)。
  2. 默认值恢复:反序列化后,transient字段将被自动恢复为该类型的默认值。
  3. 非静态transient关键字只能用于类的非静态字段。
  4. 非继承transient关键字的效果仅限于声明它的类。如果一个子类继承了一个父类,并且父类中的字段被标记为transient,那么在子类的序列化过程中,这些字段仍然不会被序列化。
  5. 性能优化:对于大对象或者包含大量数据的字段,使用transient可以减少序列化的数据量,从而提高序列化和反序列化的效率。

2、使用最佳实践

  1. 安全性:对于敏感信息,如密码或个人信息,使用transient可以防止这些信息在序列化过程中被泄露。
  2. 资源管理:对于文件句柄、数据库连接、线程等资源,不应该被序列化,因为它们通常与特定的运行时环境相关联,并且可能在序列化后不再有效。
  3. 性能考虑:如果一个字段很大,并且不需要在序列化后保留其状态,使用transient可以减少序列化的数据量,提高性能。
  4. 临时状态:如果一个字段仅用于临时状态,如缓存或中间计算结果,使用transient可以避免在序列化过程中包含这些不必要的状态。
  5. 自定义序列化:对于需要特殊序列化逻辑的字段,可以通过实现writeObjectreadObject方法,并在这些方法中显式处理transient字段。
  6. 版本控制:在类结构发生变化时,使用transient可以帮助管理字段的版本,避免因为字段的添加或删除导致序列化版本号的变更。
  7. 避免不必要的序列化:如果一个字段总是可以通过其他方式重建(例如,基于其他字段的计算),则没有必要序列化它。
  8. 文档和维护:在使用transient关键字时,应该在代码中添加适当的注释,说明为什么该字段被标记为transient,以便于其他开发者理解和维护。

通过遵循这些最佳实践,可以有效地利用transient关键字来控制序列化过程,提高应用程序的性能和安全性。


五、自定义序列化逻辑


自定义序列化逻辑允许开发者控制对象序列化和反序列化的详细过程。在Java中,可以通过重写对象类的writeObjectreadObject方法来实现自定义序列化。这对于优化性能、处理非可序列化对象、实现版本控制或添加额外的逻辑非常有用。

1、自定义序列化步骤:

第一步,重写writeObject方法

  • 这个方法是在对象序列化时被调用的。
  • 可以在这里添加自定义的序列化逻辑,比如只序列化对象的某些字段,或者在序列化之前进行某些计算或检查。

第二步,重写readObject方法

  • 这个方法是在对象反序列化时被调用的。
  • 可以在这里添加自定义的反序列化逻辑,比如在反序列化后恢复对象的状态,或者在对象状态被读取之后执行某些操作。

第三步,使用private void readObject(ObjectInputStream in)private void writeObject(ObjectOutputStream out)

  • 这些方法是私有的,只能在类的内部被调用。
  • 它们不能被类的外部调用,这保证了序列化和反序列化过程的安全性。

### 2、示例代码:
import java.io.*;class MyObject implements Serializable {private int value;private transient int transientValue;public MyObject(int value) {this.value = value;this.transientValue = 0;}// 自定义序列化逻辑private void writeObject(ObjectOutputStream out) throws IOException {out.defaultWriteObject(); // 序列化非transient字段// 可以添加额外的序列化逻辑out.writeInt(transientValue + 100); // 示例:修改transient字段的值}// 自定义反序列化逻辑private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {in.defaultReadObject(); // 反序列化非transient字段// 可以添加额外的反序列化逻辑transientValue = in.readInt() - 100; // 示例:根据序列化逻辑恢复transient字段的值}// 其他业务逻辑...
}

3、自定义序列化的最佳实践:


(1)、使用defaultWriteObjectdefaultReadObject

  • writeObjectreadObject方法中,首先调用defaultWriteObject()defaultReadObject()方法,以确保非transient字段被正确序列化和反序列化。

(2)、处理transient字段

  • 如果类中有transient字段,需要在writeObjectreadObject方法中显式处理这些字段的序列化和反序列化。

(3)、异常处理

  • 在自定义序列化方法中,适当处理可能发生的异常,如IOException

(4)、版本控制

  • 当类的字段发生变化时,通过在readObject方法中添加逻辑来处理旧版本的序列化对象。

(5)、安全性

  • 由于writeObjectreadObject方法是私有的,确保它们不会被外部调用,避免安全风险。

(6)、性能优化

  • 优化自定义序列化逻辑,以减少不必要的I/O操作,提高性能。

(7)、测试

  • 彻底测试自定义序列化逻辑,确保序列化和反序列化过程的正确性和稳定性。

通过实现自定义序列化逻辑,开发者可以更细致地控制对象的序列化过程,满足特定的需求,如安全性、性能优化或复杂的状态管理。


六、版本控制serialVersionUID


在Java序列化机制中,serialVersionUID是一个非常重要的概念,它用于在序列化和反序列化过程中确保类的版本兼容性。以下是关于serialVersionUID的详细说明:


### 1、什么是`serialVersionUID`?

serialVersionUID是一个唯一的版本标识符,用于区分序列化的对象属于哪个类的不同版本。当一个对象被序列化时,它的类定义中的serialVersionUID会被包含在序列化数据中。在反序列化时,Java运行时环境会检查序列化数据中的serialVersionUID与类定义中的serialVersionUID是否匹配。


2、为什么需要serialVersionUID


(1)、版本控制:当类的实现发生变化时(例如,添加或删除字段),serialVersionUID帮助确保类的序列化版本是否兼容。

(2)、反序列化兼容性:如果序列化对象的类定义与反序列化时的类定义不匹配,Java运行时环境会根据serialVersionUID来判断是否可以安全地进行反序列化。

(3)、避免序列化错误:如果没有显式声明serialVersionUID,Java运行时环境会基于类的细节自动生成一个。如果类的实现发生变化,自动生成的serialVersionUID也会变化,这可能导致反序列化时出现InvalidClassException错误。


3、如何使用serialVersionUID


(1)、声明serialVersionUID

private static final long serialVersionUID = 1L;

这个声明应该放在类的第一行,作为一个静态常量。

(2)、选择serialVersionUID的值

  • 通常,serialVersionUID是一个长整型(long)值。
  • 可以手动指定一个固定的值,或者使用工具(如serialver工具)来生成。

(3)、更新serialVersionUID

  • 当类的序列化状态发生变化时(例如,添加、删除或修改字段),应该更新serialVersionUID
  • 如果希望保持与旧版本的兼容性,可以选择不更新serialVersionUID

4、最佳实践:


(1)、显式声明:即使类的实现没有变化,也建议显式声明serialVersionUID,以避免自动生成的值在未来发生变化。

(2)、版本管理:在类文档中记录serialVersionUID的更改,以及每次更改的原因。

(3)、兼容性考虑:在设计可序列化的类时,考虑未来可能的变更,并评估这些变更对序列化兼容性的影响。

(4)、使用工具:可以使用serialver工具来帮助生成serialVersionUID

(5)、测试:在更改类的实现后,彻底测试序列化和反序列化过程,确保serialVersionUID的更改不会破坏现有功能。

(6)、文档化:在类文档中清楚地说明serialVersionUID的使用和任何相关的版本兼容性问题。


通过正确使用serialVersionUID,可以有效地管理类的序列化版本,确保序列化和反序列化过程的兼容性和稳定性。这对于维护大型系统和长期运行的应用程序尤为重要。


总之,Java序列化封装了对象转字节流的细节,提供了简洁的API,但同时也需要我们对其原理和用法有更深入的理解,才能在实践中发挥其最大价值。

期待在未来的技术进化中,序列化会变得更加易于使用和扩展,为分布式系统、大数据处理等领域贡献新的引擎。让我们拭目以待吧!

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

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

相关文章

如何为个人网站部署SSL安全证书,以实现网站的 HTTPS 加密协议访问?

哈喽,大家好呀!这里是码农后端。完成了域名的备案与解析后,就可以通过域名来访问我们的网站了。本篇将介绍如何为我们的网站部署SSL安全证书,实现网站的 HTTPS 加密协议访问。 1、购买SSL证书 未进行SSL证书部署,访问网…

数据保存MySQL语法

文章目录 一、导包二、链接数据库三、创建操作工具【游标 cursor】四、执行命令1.创建表2.插入数据 五、数据库提交 一、导包 import pymysql二、链接数据库 db pymysql.connect(hostlocalhost,port3306,userroot,passwordroot123,dbpython案例,charsetutf8 )三、创建操作工…

回答篇二:测试开发高频面试题目

引用之前文章:测试开发高频面试题目 本篇文章是回答篇(持续更新中) 1. 在测试开发中使用哪些自动化测试工具和框架?介绍一下你对其中一个工具或框架的经验。 a. 测试中经常是用的自动化测试工具和框架有Selenium、Pytest、Postman…

调整表格大小

方法一:使用鼠标拖动表格边框或右下角的调整控点 在Word文档中,选中要缩小的表格,将鼠标指针放在表格的边框线上,直到指针变成双箭头的形状。 按住鼠标左键,拖动边框线,调整表格的宽度或高度。如果同时按住…

leetcode328-Odd Even Linked List

题目 给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。 第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。 请注意,偶数组和奇数组内…

AI视频教程下载:使用ChatGPT进行商务写作

你将学到什么? 学习如何将ChatGPT集成到你的写作过程中,并有效地将其用作商务写作的个人写作助手。 学习如何使用ChatGPT生成想法,提高你的书面沟通的结构、清晰度和连贯性。 你将学习使用ChatGPT的最佳实践,包括如何自定义其设…

Win10版本TDengine使用分享

软件介绍 TDengine是一款开源、高性能、可扩展的时间序列数据库(TSDB)。它由涛思数据公司开发,专为处理大规模时间序列数据而设计。时间序列数据是指按时间顺序排列的数据点序列,广泛应用于物联网、大数据分析、金融等领域。TDen…

Redis解决缓存一致性问题

文章目录 ☃️概述☃️数据库和缓存不一致采用什么方案☃️代码实现☃️其他 ☃️概述 由于我们的 缓存的数据源来自于数据库, 而数据库的 数据是会发生变化的, 因此,如果当数据库中 数据发生变化,而缓存却没有同步, 此时就会有 一致性问题存在, 其后果是: 用户使用缓存中的过…

代码随想录算法训练营第六天| 454.四数相加II、383. 赎金信、15. 三数之和、18.四数之和

454.四数相加II 给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] B[j] C[k] D[l] 0。 解题思路 感觉这道题目就是傻大粗,没啥可以值得学习的。反正for循环就完事了。 解法1 public int fourSumCount(int[] …

DSP6657 GPIO中断学习

1 简介 使用创龙板卡的KEY2按键通过中断的方式控制LED3的亮灭 2 中断学习 在C665x设备上,CPU中断是通过C66x CorePac中断控制器进行配置的。该中断控制器允许最多128个系统事件被编程到任意12个CPU可屏蔽中断输入(CPUINT4至CPUINT15)、CPU…

短剧解说一键生成原创文案的快速方法

如今短剧创作火的一塌糊涂,它们以其简洁明了的剧情、生动有趣的角色和紧凑的节奏,吸引了大量观众的关注。因此,它所带来的流量是非常巨大,不少人将流量的获取瞄准了短剧创作领域以及短剧解说领域。而对于短剧解说人员来讲&#xf…

c++动态内存管理/模板

一,动态内存管理 (1)C语言和c区别 在C语言中,我们常用malloc和calloc申请空间,用free来释放空间,而现在我们更多的情况下用new来申请空间,用delete来释放空间。 申请一个空间: i…

微服务项目收获和总结---第5天(定时发布)

延迟任务 目录 延迟任务技术对比: Redis实现定时任务:​编辑新增任务:取消任务:拉取任务:Zset定时刷新数据到List中:分布式锁实现定时任务只刷新一次: 技术对比: Redis实现定时任…

香橙派 AIpro 昇腾 Ascend C++ 分类模型适配

香橙派 AIpro 昇腾 Ascend C 分类模型适配 flyfish 文章目录 香橙派 AIpro 昇腾 Ascend C 分类模型适配前言一、PyTorch官网resnet模型处理方式1、PyTorch模型 导出 onnx格式2、完整测试 输出top1结果3、完整测试 输出top5结果 二、YOLOv8官网resnet模型Python处理方式三、昇腾…

web学习笔记(五十九)

目录 1.style样式 1.1作用域 scoped 1.2 less和 sass 1.3 less和 sass两者的区别 2. 计算属性computed 3. 响应式基础reactive() 4. 什么是MVVM? 1.style样式 1.1作用域 scoped scoped表示样式作用域,把内部的样式仅限于当前组件模板生效,其…

云衔科技:为什么推荐使用zoho crm客户管理系统?

在当今快速变化的商业环境中,企业对高效、智能化的客户关系管理(CRM)系统的需求日益增长。Zoho CRM,作为全球领先的企业级CRM解决方案提供商,凭借其全面的功能、高度的可定制性、以及无缝集成的生态系统,成…

mybatis plus leftjoin 表如何去掉自动添加deleted=0条件

Mybatis Plus 在进行多表查询时,默认会添加逻辑删除的过滤条件,例如 deleted0,这是为了在查询时自动过滤掉被逻辑删除的数据。如果你希望在某个 LEFT JOIN 查询中去掉这个自动添加的条件,可以通过以下方式实现: 全局配…

探寻导师:2024年最新研究生导师评价汇总

随着考研热潮的不断升温,对于研究生导师的选择变得愈发重要。导师不仅关系到研究生的学术成长,还直接影响到未来的职业发展。然而,如何找到一位合适的导师成为许多考生头疼的问题。为了帮助广大考生更好地选择导师,整理了最新最全…

Nature 正刊!瑞典于默奥大学研究团队在研究全球河流和溪流的甲烷排放中取得新进展

甲烷(CH4)是一种强有力的温室气体,自工业革命以来,其在大气中的浓度增加了两倍。有证据表明,全球变暖增加了淡水生态系统的 CH4 排放,为全球气候提供了积极的反馈。然而,对于河流和溪流来说,甲烷排放的控制…

区间类贪心,蓝桥云课 打折

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 0打折 - 蓝桥云课 (lanqiao.cn) 二、解题报告 1、思路分析 思路很简单&am…