Java 序列化:Serializable 接口与 ObjectOutputStream
Java 序列化机制允许将对象转换为字节序列,以便存储到硬盘或在网络中传输。通过 Serializable 接口和 ObjectOutputStream,开发者可以轻松实现对象的持久化。以下是其具体使用方法与核心注意事项。
一、 理解序列化与反序列化
区分两个核心概念:
- 序列化:将 Java 对象转换为字节序列的过程。
- 反序列化:将字节序列恢复为 Java 对象的过程。
掌握两个主要用途:
- 将对象状态持久化保存到文件或数据库中。
- 在网络通信中传输对象字节序列。
二、 实现序列化的具体步骤
遵循以下步骤即可完成对象序列化与反序列化操作。
1. 定义可序列化类
实现 java.io.Serializable 接口。该接口是一个标记接口,不包含任何方法。
添加 serialVersionUID 字段。这是一个版本号,用于验证序列化对象的发送者和接收者是否加载了兼容的类。如果不显式定义,JVM 会根据类详情自动生成,一旦类结构发生变化(如新增字段),自动生成的 ID 会改变,导致反序列化失败。
import java.io.Serializable;
public class User implements Serializable {
// 显式定义版本号,保证类结构修改后仍能兼容旧数据
private static final long serialVersionUID = 1L;
private String name;
private transient String password; // transient 关键字修饰的字段不参与序列化
public User(String name, String password) {
this.name = name;
this.password = password;
}
@Override
public String toString() {
return "User{name='" + name + "', password='" + password + "'}";
}
}
2. 执行序列化(写入对象)
创建文件输出流(FileOutputStream)指向目标文件。
包装文件输出流为对象输出流(ObjectOutputStream)。
调用 writeObject(Object obj) 方法将对象写入流。
关闭流以释放资源。
import java.io.*;
public class SerializationDemo {
public static void serialize(String fileName) {
ObjectOutputStream out = null;
try {
// 创建对象输出流
out = new ObjectOutputStream(new FileOutputStream(fileName));
// 创建对象并序列化
User user = new User("张三", "123456");
out.writeObject(user);
System.out.println("序列化成功,对象已写入 " + fileName);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3. 执行反序列化(读取对象)
创建文件输入流(FileInputStream)指向源文件。
包装文件输入流为对象输入流(ObjectInputStream)。
调用 readObject() 方法读取对象。
强制转换返回的对象为原始类型。
注意:读取对象的顺序必须与写入时的顺序一致。
public static void deserialize(String fileName) {
ObjectInputStream in = null;
try {
// 创建对象输入流
in = new ObjectInputStream(new FileInputStream(fileName));
// 读取对象并反序列化
User user = (User) in.readObject();
System.out.println("反序列化成功:" + user);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
三、 关键控制点与常见陷阱
处理字段序列化时,需注意以下规则和特性。
| 特性 | 关键字/概念 | 行为描述 |
|---|---|---|
| 屏蔽字段 | transient |
被 transient 修饰的字段不会被序列化。反序列化后,该字段值为默认值(如 null 或 0)。 |
| 静态字段 | static |
静态变量属于类,不属于对象状态,因此不会被序列化。 |
| 版本控制 | serialVersionUID |
建议显式声明。若未声明,JVM 自动生成的 ID 对类结构极其敏感,修改类后可能导致 InvalidClassException。 |
四、 完整运行流程
下图描述了从 Java 对象到二进制数据,再恢复为 Java 对象的完整数据流向:
graph LR
A[Java Object] -->|writeObject| B[ObjectOutputStream]
B -->|转换| C[Binary Stream]
C -->|存储/传输| D[File / Network]
D -->|读取| E[ObjectInputStream]
E -->|readObject| F[Restored Java Object]
五、 综合代码示例
运行以下代码,验证序列化与反序列化的完整过程,重点观察 transient 字段的变化。
import java.io.*;
public class CompleteDemo {
public static void main(String[] args) {
String filePath = "user.ser";
// 1. 序列化操作
serializeUser(filePath);
// 2. 反序列化操作
deserializeUser(filePath);
}
public static void serializeUser(String filePath) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath))) {
User user = new User("李四", "secretPass");
oos.writeObject(user);
// oos.close(); // Try-with-resources 会自动关闭
} catch (IOException e) {
e.printStackTrace();
}
}
public static void deserializeUser(String filePath) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath))) {
User user = (User) ois.readObject();
// 输出结果中 password 将为 null,因为它被 transient 修饰
System.out.println("读取到的对象: " + user);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private transient String password; // 敏感信息不序列化
public User(String name, String password) {
this.name = name;
this.password = password;
}
@Override
public String toString() {
return "User{name='" + name + "', password='" + password + "'}";
}
}
暂无评论,快来抢沙发吧!