Java 序列化是将对象的状态转换为可存储或可传输的字节流的过程,而反序列化则是将字节流恢复为原始对象的过程。这一机制是 Java 中对象持久化(如保存到文件)、网络传输(如 RPC 调用)的基础。本文将从核心概念、实现方式、关键细节到高级特性,全面解析 Java 序列化。
在 Java 中,对象是存在于内存中的动态数据结构。但在实际开发中,我们常需要将对象 “脱离内存”:比如保存到文件(持久化)、通过网络发送给另一台服务器(跨进程通信)。此时,就需要将对象的状态(成员变量的值)转换为字节流 —— 这一过程即序列化;而接收方通过字节流重建对象的过程则称为反序列化。
核心作用:
- 对象持久化:将对象状态保存到磁盘(如序列化到文件),下次程序启动时可恢复。
- 网络传输:在分布式系统中,通过序列化将对象转为字节流,通过网络传输到远程节点后再反序列化。
- 跨进程通信:如 RMI(远程方法调用)中,参数和返回值需通过序列化传递。
Java 序列化通过 Serializable 接口(标记接口)和 ObjectOutputStream/ObjectInputStream 类实现,步骤简单但需严格遵循规范。
Serializable 是一个标记接口(无任何抽象方法),仅用于告诉 JVM:“这个类的对象可以被序列化”。若类未实现该接口,序列化时会抛出 NotSerializableException。
import java.io.Serializable;
关键:transient 关键字修饰的成员变量不会被序列化,反序列化时会使用默认值(如 null 字符串、0 整数)。常用于敏感信息(如密码)或无需持久化的临时数据。
ObjectOutputStream 提供 writeObject(Object obj) 方法,将对象转为字节流并写入输出流(如文件输出流)。
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;public class SerializeDemo {public static void main(String[] args) {User user = new User("张三", 25, "123456");String filePath = "user.ser";
执行后,会生成 user.ser 文件,存储 User 对象的字节流(内容为二进制,无法直接阅读)。
ObjectInputStream 提供 readObject() 方法,从输入流(如文件输入流)读取字节流,重建为对象。
import java.io.FileInputStream;
import java.io.ObjectInputStream;public class DeserializeDemo {public static void main(String[] args) {String filePath = "user.ser";try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath))) {User user = (User) ois.readObject();
输出结果:
对象反序列化成功:User{name='张三', age=25, password='null'}
- 注意:
password 字段被 transient 修饰,反序列化后为 null(默认值),符合预期。
在序列化过程中,JVM 会为每个可序列化类生成一个唯一标识 ——serialVersionUID(序列化版本号),用于反序列化时验证 “字节流对应的类” 与 “当前类” 是否为同一版本。
- 若未显式声明,JVM 会根据类的结构(成员变量、方法等)自动计算
serialVersionUID。
- 当类结构发生变化(如新增 / 删除成员变量、修改方法),自动计算的
serialVersionUID 会改变,导致反序列化时抛出 InvalidClassException(“类版本不匹配”)。
最佳实践:显式声明 serialVersionUID,固定类的版本标识,即使类结构变化,只要版本号一致,仍可反序列化(新增字段用默认值,删除字段被忽略)。
public class User implements Serializable {
-
静态字段不参与序列化
静态变量属于类(而非对象),序列化仅保存对象状态,因此静态字段不会被序列化。反序列化后,静态字段的值取决于当前类的静态变量值,而非序列化时的值。
public class User implements Serializable {private static String version = "1.0";
-
父类未实现 Serializable 时的处理
若子类实现
Serializable,但父类未实现,则:
- 父类的成员变量不会被序列化(反序列化时使用父类无参构造函数初始化,若父类无无参构造,会抛出
InvalidClassException)。
- 解决:父类也实现
Serializable,或确保父类有 public 无参构造。
-
所有成员变量必须可序列化
若对象的成员变量是自定义类,该类必须也实现
Serializable,否则序列化时会抛出
NotSerializableException。
例外:用
transient 修饰不可序列化的成员变量(反序列化时为默认值)。
-
单例模式与序列化的冲突
单例模式要求全局只有一个实例,但默认序列化会破坏单例:反序列化时会通过反射创建新对象,而非使用原单例。
解决:重写 readResolve() 方法,指定反序列化时返回单例对象:
public class Singleton implements Serializable {private static final long serialVersionUID = 1L;private static Singleton instance = new Singleton();private Singleton() {}
默认序列化会按成员变量逐一处理,但有时需要自定义逻辑(如加密敏感字段、忽略动态生成的数据)。可通过重写 writeObject() 和 readObject() 方法实现。
public class User implements Serializable {private static final long serialVersionUID = 1L;private String name;private int age;private String password;
- 注意:
writeObject() 和 readObject() 必须是 private 方法,由 JVM 在序列化 / 反序列化时反射调用,无需手动调用。
Java 原生序列化存在性能较差、体积较大、版本兼容性复杂等问题,实际开发中常用更高效的方案替代:
- JSON:轻量级文本格式(如 Jackson、Gson 库),可读性强,适合跨语言传输。
- Protocol Buffers:Google 推出的二进制格式,体积小、效率高,适合高性能场景(需定义
.proto 协议)。
- Hessian:二进制协议,支持跨语言,适合 RPC 框架(如 Dubbo)。
Java 序列化是对象持久化和网络传输的基础,核心通过 Serializable 接口标记类,ObjectOutputStream/ObjectInputStream 处理字节流。关键注意点:
- 显式声明
serialVersionUID 确保版本兼容。
transient 修饰无需序列化的字段。
- 自定义
writeObject()/readObject() 控制序列化逻辑。
- 原生序列化有局限,复杂场景可选用 JSON、Protobuf 等替代方案。
掌握序列化机制,能帮助你更好地理解分布式系统、缓存框架(如 Redis 存储对象)的底层原理,避免版本冲突、数据丢失等常见问题。