
Java 序列化流的核心作用是将内存中的Java对象转换为字节序列(序列化),或将字节序列还原为内存中的Java对象(反序列化),实现对象的持久化存储(如写入文件)或网络传输(如分布式系统间对象传递)。以下是序列化流的完整解析,涵盖核心概念、API使用、规则与注意事项。
一、核心概念
1. 序列化(Serialization)
将Java对象(包含属性、状态信息)转换为二进制字节流的过程,目的是脱离JVM内存环境,实现对象的“异地迁移”或“持久化保存”。
2. 反序列化(Deserialization)
将序列化生成的字节流还原为原始Java对象的过程,还原后的对象与原对象状态一致(属性值相同,独立于原对象)。
3. 序列化的核心前提
一个类要能被序列化,必须实现 java.io.Serializable 接口(标记接口,无任何抽象方法,仅用于告知JVM该类支持序列化),否则序列化时会抛出 NotSerializableException。
二、核心序列化/反序列化流 API
Java IO 提供了专门用于对象序列化的字节流,核心是 ObjectOutputStream(序列化流)和 ObjectInputStream(反序列化流),均继承自字节流父类,依赖字节流实现底层存储/传输。
1. 序列化流:ObjectOutputStream
核心作用
将Java对象写入字节输出流(可关联文件、网络套接字等),生成序列化字节序列。
关键API
使用示例(对象写入文件)
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
// 待序列化的类:必须实现 Serializable 接口
class User implements Serializable {
// 序列化版本号:用于反序列化时校验类结构是否一致
private static final long serialVersionUID = 1L;
private String username;
private int age;
// 瞬态字段:不会被序列化(transient 修饰)
private transient String password;
// 构造方法、getter/setter 省略
public User(String username, int age, String password) {
this.username = username;
this.age = age;
this.password = password;
}
@Override
public String toString() {
return "User{username='" + username + "', age=" + age + ", password='" + password + "'}";
}
}
public class SerializationDemo {
public static void main(String[] args) {
// 1. 创建待序列化对象
User user = new User("zhangsan", 25, "123456");
// 2. 构建 ObjectOutputStream(依赖 FileOutputStream 写入文件)
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
// 3. 序列化对象并写入文件
oos.writeObject(user);
System.out.println("对象序列化成功,已写入 user.ser 文件");
} catch (Exception e) {
e.printStackTrace();
}
}
}
2. 反序列化流:ObjectInputStream
核心作用
从字节输入流中读取序列化字节序列,还原为Java对象。
关键API
使用示例(从文件读取并反序列化对象)
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializationDemo {
public static void main(String[] args) {
// 1. 构建 ObjectInputStream(依赖 FileInputStream 读取文件)
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
// 2. 反序列化对象:强制类型转换为 User 类型
User user = (User) ois.readObject();
System.out.println("对象反序列化成功:" + user);
// 注意:transient 字段 password 未被序列化,反序列化后为 null
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果:对象反序列化成功:User{username='zhangsan', age=25, password='null'}(password 为 transient,未被序列化)
三、序列化核心规则与特性
1. 关键字 transient:排除序列化字段
作用:被 transient 修饰的字段,序列化时会被忽略,反序列化后该字段值为默认值(引用类型为 null,基本类型为 0/false 等);
适用场景:无需持久化/传输的临时字段(如缓存数据、敏感信息(密码))。
2. 序列化版本号 serialVersionUID
作用:serialVersionUID 被称为序列化 ID,它是决定 Java 对象能否反序列化成功的重要因子。在反序列化时,Java 虚拟机会把字节流中的 serialVersionUID 与被序列化类中的 serialVersionUID 进行比较,如果相同则可以进行反序列化,否则就会抛出序列化版本不一致的异常。
添加方式:
1)添加一个默认版本的序列化 ID:
private static final long serialVersionUID = 1L。2)添加一个随机生成的不重复的序列化 ID。
private static final long serialVersionUID = -2095916884810199532L;3)添加 @SuppressWarnings 注解。
@SuppressWarnings("serial")
核心作用
用于反序列化时校验“序列化时的类结构”与“反序列化时的类结构”是否一致,避免类结构变更导致反序列化失败。
生成与使用
手动指定:推荐在序列化类中显式声明 private static final long serialVersionUID = 固定值;(如示例中的 1L);
自动生成:IDE(IDEA/Eclipse)可自动生成唯一的序列化版本号(基于类名、字段、方法等计算);
未指定的风险:若类结构变更(如新增/删除字段),JVM会重新计算版本号,导致反序列化时抛出 InvalidClassException(版本号不匹配);
指定后的优势:即使类结构小幅变更(如新增非必需字段),只要版本号不变,反序列化仍可成功(新增字段取默认值,删除字段忽略)。
3. static 字段不参与序列化
static 字段属于类级别的变量,不属于单个实例对象的状态,因此序列化仅针对实例字段,static 字段不会被序列化/反序列化(反序列化后读取的是当前类的 static 字段值,而非序列化时的旧值)。
4. 父类序列化规则
若父类实现了 Serializable 接口:子类序列化时,会自动序列化父类的所有非 transient、非 static 实例字段;
若父类未实现 Serializable 接口:父类必须提供无参构造方法,否则子类反序列化时会抛出 InvalidClassException;此时子类仅序列化自身的字段,父类字段通过无参构造方法初始化(取默认值)。
5. 集合/数组的序列化
Java 内置集合(ArrayList、HashMap 等)均已实现 Serializable 接口,可直接序列化(需保证集合中的元素也实现 Serializable);
数组(基本类型数组、引用类型数组)均可序列化(引用类型数组需保证数组元素实现 Serializable)。
示例(序列化 ArrayList 集合):
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("userList.ser"))) {
List<User> userList = new ArrayList<>();
userList.add(new User("zhangsan", 25, "123456"));
userList.add(new User("lisi", 28, "654321"));
oos.writeObject(userList); // 集合序列化
} catch (Exception e) {
e.printStackTrace();
}
四、序列化的常见场景
对象持久化:将对象写入本地文件、数据库BLOB字段,实现对象状态的持久化存储(如用户会话持久化、配置对象保存);
网络传输:分布式系统(如Dubbo、RMI)中,通过网络传输对象时,需先将对象序列化为字节流,传输后再反序列化为对象;
缓存存储:将复杂对象存入缓存(如Redis)时,需先序列化为字节流或JSON(序列化的一种轻量形式),读取时反序列化。
五、注意事项与坑点
敏感信息泄露:序列化会将对象的所有非 transient 字段写入字节流,若包含密码、令牌等敏感信息,可能导致泄露,需用 transient 修饰或加密存储;
类结构变更风险:未指定 serialVersionUID 时,类结构变更会导致反序列化失败,建议所有序列化类显式声明版本号;
流关闭顺序:使用 ObjectOutputStream/ObjectInputStream 时,需保证流正常关闭(推荐使用 try-with-resources 语法,自动关闭流);
反序列化安全:恶意构造的序列化字节流可能导致安全漏洞(如远程代码执行),JDK 提供了 ObjectInputFilter 用于过滤非法反序列化对象,生产环境需开启安全校验。
六、总结
核心流:ObjectOutputStream(序列化,写对象)、ObjectInputStream(反序列化,读对象),依赖字节流实现底层IO;
核心前提:待序列化类必须实现 Serializable 接口,推荐显式声明 serialVersionUID;
字段控制:transient 排除序列化字段,static 字段不参与序列化;
核心规则:父类序列化需实现接口或提供无参构造,集合/数组可直接序列化(元素需满足序列化要求);
核心场景:对象持久化、网络传输、缓存存储,是Java分布式系统和持久化方案的基础。