Java对象序列化与反序列化详解
从现在开始,我们要努力学习啦!今天我给大家带来《Java对象序列化与反序列化基础教程》,感兴趣的朋友请继续看下去吧!下文中的内容我们主要会涉及到等等知识点,如果在阅读本文过程中有遇到不清楚的地方,欢迎留言呀!我们一起讨论,一起学习!
Java中实现对象序列化与反序列化的核心是通过实现Serializable接口将对象转换为字节流并恢复,其中被transient和static修饰的字段以及父类未实现Serializable时的非静态字段不会被序列化,因此在序列化过程中这些字段的状态不会被保存或恢复,从而确保敏感信息不被持久化、共享状态不被重复记录,并正确处理继承关系下的对象重建,最终保证序列化机制的安全性与一致性。
Java中实现对象的序列化与反序列化,核心在于将一个Java对象的状态转换为字节流,以便存储或在网络中传输,之后再将字节流恢复为对象。这听起来有点像魔法,但实际上是Java I/O体系里一个非常基础且强大的功能。简单来说,就是把活生生的对象“冻结”成数据,需要时再“解冻”复活。
解决方案
要让一个Java对象具备序列化能力,最直接的方法就是让它的类实现java.io.Serializable
接口。这个接口是个标记接口,它本身没有任何方法需要实现,仅仅是告诉JVM:“嘿,我这个类的对象可以被序列化了!”。
序列化过程主要用到java.io.ObjectOutputStream
,它能将对象写入一个输出流(比如文件流或网络流)。反序列化则使用java.io.ObjectInputStream
,从输入流中读取字节并重构对象。
我们来看一个基础的例子:
import java.io.*; // 1. 定义一个可序列化的类 class User implements Serializable { private static final long serialVersionUID = 1L; // 推荐显式定义 private String name; private int age; private transient String password; // 标记为transient的字段不会被序列化 public User(String name, int age, String password) { this.name = name; this.age = age; this.password = password; } @Override public String toString() { return "User{name='" + name + "', age=" + age + ", password='" + password + "'}"; } // 省略getter/setter public String getName() { return name; } public int getAge() { return age; } public String getPassword() { return password; } } public class SerializationDemo { public static void main(String[] args) { User user = new User("张三", 30, "mySecretPass"); String filePath = "user.ser"; // 序列化文件路径 // 序列化 try (FileOutputStream fileOut = new FileOutputStream(filePath); ObjectOutputStream out = new ObjectOutputStream(fileOut)) { out.writeObject(user); System.out.println("User对象已序列化到 " + filePath); System.out.println("原始对象: " + user); } catch (IOException i) { i.printStackTrace(); } // 反序列化 User deserializedUser = null; try (FileInputStream fileIn = new FileInputStream(filePath); ObjectInputStream in = new ObjectInputStream(fileIn)) { deserializedUser = (User) in.readObject(); System.out.println("User对象已从 " + filePath + " 反序列化。"); System.out.println("反序列化对象: " + deserializedUser); // 注意:transient字段的值为默认值 System.out.println("反序列化后密码: " + deserializedUser.getPassword()); // 会是null } catch (IOException i) { i.printStackTrace(); } catch (ClassNotFoundException c) { System.out.println("User class not found"); c.printStackTrace(); } } }
运行这段代码,你会发现password
字段在反序列化后变成了null
,这正是transient
关键字的作用。
Java对象序列化时,哪些字段会被忽略?
这问题问得挺好,因为不是所有字段都会乖乖地被序列化。在Java的默认序列化机制中,有几种情况下的字段是会被“跳过”的:
首先,也是最常见的,就是被transient
关键字修饰的字段。transient
直译过来是“瞬态的”、“临时的”,用它来标记一个字段,就等于告诉JVM:“这个字段我不想让它参与序列化过程,它的值不应该被保存下来。” 为什么会有这种需求呢?可能是这个字段存储的是敏感信息(比如密码),不希望它以明文形式出现在序列化数据中;也可能是这个字段是运行时动态计算出来的,或者它引用了一个不能被序列化(或不应该被序列化)的对象,比如一个线程、一个文件句柄,或者一个数据库连接。序列化后,这些transient
字段的值会被恢复成其类型的默认值(对象类型为null
,基本类型为0、false
等)。
其次,static
(静态)字段也不会被序列化。这其实很好理解,静态字段属于类本身,而不是属于类的某个特定对象实例。序列化保存的是对象的状态,而静态字段是所有对象共享的,它不构成任何一个对象实例的独特状态。所以,无论你序列化多少个对象实例,静态字段的值都不会被保存或恢复。它在反序列化时,依然会保持其在当前JVM中的静态值。
最后,如果一个类的父类没有实现Serializable
接口,那么该父类的非transient
、非static
字段也不会被序列化。当子类被序列化时,只有子类自己的可序列化字段会被保存。反序列化时,父类的字段会调用父类的无参构造器来初始化。这也是为什么如果你的父类没有无参构造器,而子类又实现了Serializable
,反序列化时可能会抛出InvalidClassException
,因为它找不到合适的构造器来初始化父类部分。
理解这些“例外”非常重要,它能帮助我们更好地设计可序列化的类,避免数据泄露或不必要的复杂性。
为什么需要给可序列化类定义serialVersionUID?
噢,serialVersionUID
,这玩意儿初看起来有点神秘,但它在Java序列化机制中扮演着一个至关重要的角色,尤其是在你对类结构进行修改之后。简单来说,它就像是类的一个“版本号”或者“指纹”,用来在序列化和反序列化时进行兼容性检查。
当你序列化一个对象时,JVM会把这个对象的serialVersionUID
也写入到序列化流中。当你在另一个JVM或者同一个JVM的不同时间点尝试反序列化这个对象时,JVM会读取流中的serialVersionUID
,然后拿它与当前类定义中的serialVersionUID
进行比较。
如果这两个ID不匹配,JVM就会认为“哎呀,这个对象当初序列化的时候,它的类定义和现在的不一样了!”然后,它会毫不留情地抛出java.io.InvalidClassException
异常。这意味着你无法成功反序列化那个对象。
那么,什么时候这个ID会不匹配呢?当你对一个已经实现了Serializable
接口的类进行修改时,比如添加、删除、修改字段,或者改变了字段的类型,甚至改变了类名、接口实现等,如果没有显式定义serialVersionUID
,JVM会根据类的结构自动生成一个。一旦类结构发生变化,自动生成的serialVersionUID
就会不同。这就是问题所在:你修改了类,即使是很小的改动,之前的序列化数据可能就无法被新的类版本反序列化了。
所以,显式定义private static final long serialVersionUID = 1L;
(或者任何你觉得合适的版本号)就变得非常重要。一旦你手动定义了它,即使你后续对类进行了添加字段、删除字段等兼容性修改,只要你不改变这个serialVersionUID
的值,JVM在反序列化时就不会因为版本号不匹配而报错。当然,这只是解决了版本号匹配的问题,数据兼容性(比如新加的字段在老数据中是默认值,老数据中的字段在新类中被删除了怎么办)还需要你自己去处理,比如通过readObject()
和writeObject()
方法进行自定义序列化逻辑。
我个人习惯是,只要一个类实现了Serializable
,我就会立即给它加上serialVersionUID
,通常从1L
开始。这就像是给你的代码买了一份保险,在未来类结构变化时,能避免很多不必要的兼容性问题。
Java序列化在实际开发中有哪些应用场景?
Java序列化在日常开发中其实无处不在,尽管很多时候我们可能没有直接去调用ObjectOutputStream
和ObjectInputStream
,但它常常作为底层机制被各种框架和库所利用。
最直观的一个应用场景就是对象的持久化。如果你想把一个Java对象的状态保存到磁盘上,以便程序关闭后下次启动还能恢复,序列化就是最直接的方式。比如,一个简单的配置对象,或者一个游戏中的存档,都可以通过序列化直接写入文件。虽然现在更流行用JSON、XML或数据库来存储数据,但对于一些简单、内部使用且Java特有的对象,序列化依然很方便。
接着,网络传输是序列化的一个大舞台。在分布式系统中,服务之间经常需要传递对象。Java的RMI(远程方法调用)就是基于序列化来实现的,当客户端调用远程服务器上的一个方法时,参数对象会被序列化后传输到服务器,结果对象也会被序列化后传回客户端。除了RMI,很多消息队列(如Kafka、RabbitMQ)在传输Java对象时,也可能在底层使用序列化机制,或者允许你自定义序列化器来处理Java对象。
缓存也是序列化经常出现的地方。当我们需要将对象存储到内存以外的缓存系统(如Redis、Memcached)中时,对象必须被转换成字节流。虽然这些缓存系统通常支持多种序列化格式(如JSON、Protobuf),但Java原生的序列化也是一种选择,尤其是在纯Java环境下的内部缓存。
再比如,跨进程通信。在同一个机器上,不同的Java进程之间如果需要传递复杂的Java对象,序列化也是一种有效的方式,比如通过管道或者共享内存。
甚至在某些情况下,深拷贝一个对象,你也可以通过先将对象序列化到内存字节数组,再反序列化回来实现。这虽然不是序列化的主要目的,但确实是一种实现深拷贝的“旁门左道”,因为它会创建一个全新的对象图,而不是简单地复制引用。
当然,现在随着微服务和异构系统变得越来越普遍,JSON、Protocol Buffers等跨语言的序列化协议变得更加流行,因为它们解决了Java序列化固有的跨语言兼容性问题。但即便如此,在纯Java的生态系统内部,或者特定场景下,Java原生的序列化依然有着不可替代的地位。它简单、直接,对于那些不需要跨语言交互的内部数据结构来说,是相当高效的。
今天关于《Java对象序列化与反序列化详解》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!

- 上一篇
- PHP高效读取大文件方法解析

- 下一篇
- EAC设置WAV同步显示教程
-
- 文章 · java教程 | 26分钟前 |
- Gradle多模块依赖配置与错误规避指南
- 300浏览 收藏
-
- 文章 · java教程 | 55分钟前 |
- Log4j1迁移到Log4j2配置问题解决
- 234浏览 收藏
-
- 文章 · java教程 | 59分钟前 |
- HTTP持久连接:多请求共用Socket方法
- 472浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java记录类应用实例详解
- 491浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- JavaWebSocket心跳检测实现教程
- 450浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java解析RPG物品数据:文本文件实现教程
- 265浏览 收藏
-
- 文章 · java教程 | 2小时前 |
- JavaSMTP发邮件教程入门指南
- 201浏览 收藏
-
- 文章 · java教程 | 3小时前 |
- JUnit测试:验证返回对象与空值实例
- 455浏览 收藏
-
- 文章 · java教程 | 3小时前 |
- WildFly部署EJB与Web服务常见问题解决
- 212浏览 收藏
-
- 文章 · java教程 | 4小时前 |
- Java17final字段反射修改技巧
- 448浏览 收藏
-
- 文章 · java教程 | 12小时前 |
- Java多线程编程技巧与实战方法
- 236浏览 收藏
-
- 文章 · java教程 | 12小时前 |
- Log4j1迁移到Log4j2:XML命名空间解决方法
- 334浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 514次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 499次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- AI Mermaid流程图
- SEO AI Mermaid 流程图工具:基于 Mermaid 语法,AI 辅助,自然语言生成流程图,提升可视化创作效率,适用于开发者、产品经理、教育工作者。
- 276次使用
-
- 搜获客【笔记生成器】
- 搜获客笔记生成器,国内首个聚焦小红书医美垂类的AI文案工具。1500万爆款文案库,行业专属算法,助您高效创作合规、引流的医美笔记,提升运营效率,引爆小红书流量!
- 247次使用
-
- iTerms
- iTerms是一款专业的一站式法律AI工作台,提供AI合同审查、AI合同起草及AI法律问答服务。通过智能问答、深度思考与联网检索,助您高效检索法律法规与司法判例,告别传统模板,实现合同一键起草与在线编辑,大幅提升法律事务处理效率。
- 281次使用
-
- TokenPony
- TokenPony是讯盟科技旗下的AI大模型聚合API平台。通过统一接口接入DeepSeek、Kimi、Qwen等主流模型,支持1024K超长上下文,实现零配置、免部署、极速响应与高性价比的AI应用开发,助力专业用户轻松构建智能服务。
- 241次使用
-
- 迅捷AIPPT
- 迅捷AIPPT是一款高效AI智能PPT生成软件,一键智能生成精美演示文稿。内置海量专业模板、多样风格,支持自定义大纲,助您轻松制作高质量PPT,大幅节省时间。
- 268次使用
-
- 提升Java功能开发效率的有力工具:微服务架构
- 2023-10-06 501浏览
-
- 掌握Java海康SDK二次开发的必备技巧
- 2023-10-01 501浏览
-
- 如何使用java实现桶排序算法
- 2023-10-03 501浏览
-
- Java开发实战经验:如何优化开发逻辑
- 2023-10-31 501浏览
-
- 如何使用Java中的Math.max()方法比较两个数的大小?
- 2023-11-18 501浏览