什么是 Java 序列化与反序列化?
2026年01月19日
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
面试考察点
面试官提出这个问题,通常旨在考察以下几点:
- 对核心概念的理解:候选人是否能清晰、准确地解释序列化与反序列化的定义、目的和基本用法。
- 对机制与细节的掌握:不仅仅是知道
implements Serializable,更想知道候选人是否理解其背后的机制,如 序列化版本 UID (serialVersionUID) 的作用、transient关键字的使用,以及序列化过程的底层细节。 - 对应用场景与替代方案的认知:候选人是否能在实际开发中(如 RPC 调用、对象缓存、持久化存储)正确应用序列化,并了解其性能、安全方面的局限性以及主流的替代方案(如 JSON、Protocol Buffers)。
- 对安全与最佳实践的了解:是否意识到 Java 原生序列化的潜在安全风险(如反序列化漏洞),以及在工程实践中应遵循的规范。
核心答案
Java 序列化 是指将内存中的 Java 对象(Object)转换成一个可存储或可传输的字节序列(byte stream)的过程。 反序列化 则是其逆过程,将字节序列恢复为内存中的 Java 对象。
其核心目的是实现对象的持久化存储(如存入文件或数据库)和网络传输(如远程方法调用 RPC 中的参数传递)。一个类通过实现 java.io.Serializable 接口来声明其对象是可序列化的。
深度解析
原理/机制
序列化的本质是按照特定协议,将对象的状态信息(即成员变量的值)转换为字节流。它并不保存方法、静态变量等信息。
Serializable接口:这是一个标记接口(Marker Interface),没有任何方法,仅用于标识该类允许被序列化。serialVersionUID:这是一个至关重要的私有静态常量。它相当于类的“指纹”或版本号。在反序列化时,JVM 会比较字节流中的serialVersionUID与本地类的serialVersionUID。如果不一致,将抛出InvalidClassException。显式声明一个固定的serialVersionUID是强烈推荐的最佳实践,它可以避免因类结构发生微小变更(如增加一个无关紧要的方法)而导致的反序列化失败。transient关键字:用于修饰成员变量,表示该变量在序列化时应被忽略。常用于保存敏感信息(如密码)或不支持序列化的对象引用。
代码示例
// 1. 实现 Serializable 接口
public class User implements Serializable {
// 2. 强烈建议显式声明 serialVersionUID
private static final long serialVersionUID = 1L;
private String name;
private int age;
// 3. 使用 transient 关键字阻止序列化
private transient String password;
// 构造方法、getter、setter 省略...
}
// 序列化与反序列化过程
public class SerializationDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
User user = new User("Alice", 30, "secret");
// --- 序列化到文件 ---
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("user.dat"))) {
oos.writeObject(user); // 关键步骤
}
// --- 从文件反序列化 ---
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("user.dat"))) {
User deserializedUser = (User) ois.readObject(); // 关键步骤
System.out.println(deserializedUser.getName()); // 输出: Alice
System.out.println(deserializedUser.getPassword()); // 输出: null (因为被 transient 修饰)
}
}
}
对比分析与最佳实践
-
Java 原生序列化 vs. 其他序列化方案:
- Java 原生序列化:深度集成于 JVM,使用方便。但缺点显著:生成的字节流体积大、序列化/反序列化性能较差、存在安全漏洞风险(通过构造恶意字节流可执行任意代码)。
- JSON/XML:文本格式,人类可读,跨语言支持好,常用于 Web API。但文本解析效率低于二进制,且无法直接序列化复杂的对象图(如循环引用)。
- Protocol Buffers / Apache Thrift / Apache Avro:高效的二进制序列化方案,跨语言、高性能、体积小、有清晰的模式定义(Schema),是现代 RPC 框架(如 gRPC)和大数据系统的首选。
-
最佳实践与常见误区:
- 显式声明
serialVersionUID:这是防止不兼容反序列化的第一道防线。 - 谨慎序列化:不要序列化整个大对象图,只序列化必要的数据。
- 处理好
transient变量:反序列化后,transient变量会被设为默认值(如null, 0)。如果其值依赖于其他状态,可能需要在readObject或writeObject方法中自定义逻辑。 - 注意安全:永远不要反序列化来自不可信来源的数据。可以考虑使用白名单机制(
ObjectInputFilter,JDK 9+ 引入)来限制反序列化的类。 - 考虑替代方案:在新项目中,除非有强兼容性需求,否则应优先考虑使用 JSON(如 Jackson/Gson)或高效的二进制协议(如 Protobuf)。
- 显式声明
总结
Java 序列化是将对象转为字节流以便存储或传输的机制,通过实现 Serializable 接口来启用,核心要点在于管理好 serialVersionUID 和合理使用 transient;但由于其性能和安全缺陷,在实际高性能、跨语言系统中,更推荐使用 JSON 或 Protobuf 等现代序列化方案。