引言:什么是序列化?
序列化(Serialization)是将对象转换为字节序列的过程,反序列化(Deserialization)是将字节序列恢复为对象的过程。主要用于对象持久化和网络传输。
序列化类比:快递打包
发送方(序列化):
你有一个Java对象
↓ 序列化
变成一盒积木(字节序列)
↓ 运输
寄送快递
接收方(反序列化):
收到一盒积木
↓ 反序列化
组装成原来的Java对象
序列化就是"打包寄送",反序列化就是"拆包组装"
序列化应用场景
对象持久化:将对象保存到文件、数据库
网络传输:RMI远程调用、WebService
缓存:Redis、Memcached存储对象
Session存储:分布式Session
Serializable接口
要让一个类的对象能够序列化,必须实现java.io.Serializable接口。
基本用法
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private transient String password; // 不参与序列化
}
重要特性
- Serializable是一个标记接口(没有方法)
- 实现该接口表示"这个类的对象可以被序列化"
- 如果类没有实现Serializable,其对象将不能被序列化
序列化与反序列化
序列化对象(写入)
import java.io.*;
public class SerializationDemo {
public static void serialize(String filename, Object obj) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(filename))) {
oos.writeObject(obj);
System.out.println("序列化成功!");
}
}
public static void main(String[] args) {
Person person = new Person("张三", 25, "password123");
try {
serialize("person.ser", person);
} catch (IOException e) {
System.out.println("序列化失败: " + e.getMessage());
}
}
}
反序列化对象(读取)
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(filename))) {
return ois.readObject();
}
}
public static void main(String[] args) {
try {
Person p = (Person) deserialize("person.ser");
System.out.println("反序列化成功: " + p);
} catch (IOException | ClassNotFoundException e) {
System.out.println("反序列化失败: " + e.getMessage());
}
}
serialVersionUID
serialVersionUID是序列化版本号,用于验证序列化和反序列化的类是否兼容。
| 情况 | 说明 |
|---|---|
| 显式声明 | private static final long serialVersionUID = 1L; |
| 未声明 | 编译器根据类结构自动生成(可能不一致) |
| 版本不匹配 | 抛出InvalidClassException |
为什么需要声明serialVersionUID?
// 类修改前
public class Person implements Serializable {
private static final long serialVersionUID = 1L; // 版本1
private String name;
private int age;
}
// 类修改后(新增字段)
public class Person implements Serializable {
private static final long serialVersionUID = 1L; // 仍然是版本1
private String name;
private int age;
private String address; // 新增字段
}
// 序列化时的Person对象可以反序列化
// 新增字段address会被设为默认值null
transient关键字
transient修饰的字段在序列化时会被忽略,不保存到字节流中。
使用场景
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private String password; // 需要序列化
private transient String token; // 不需要序列化
private transient Date loginTime; // 不需要序列化
}
注意:transient字段在反序列化后会被设置为默认值(null、0、false)。
应该使用transient的场景
敏感信息:密码、密钥等安全数据
计算得出的值:缓存、派生数据
非持久化状态:临时变量、线程相关
序列化深入理解
序列化的局限性
// 不能序列化的内容:
// 1. 线程、Socket连接等运行时对象
// 2. 图形、GUI组件
// 3. 内部类(隐式包含外部类引用)
// 4. 循环引用的对象(会StackOverflow)
静态字段序列化
public class Test implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private static int count; // 静态字段不序列化
private transient int temp; // transient字段不序列化
}
// 序列化时:
// - name会被保存
// - count(静态)不会被保存
// - temp会被忽略
// 反序列化时:
// - name恢复原值
// - count保持当前JVM中的值(不恢复)
// - temp恢复为默认值0
自定义序列化
public class CustomSerializable implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private Date createdDate;
// 自定义序列化方法(可选)
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // 先默认序列化
out.writeLong(createdDate.getTime()); // 自定义处理
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject(); // 先默认反序列化
createdDate = new Date(in.readLong()); // 自定义处理
}
}
实战:用户Session序列化
import java.io.*;
import java.util.*;
public class SessionSerialization {
public static void main(String[] args) {
UserSession session = new UserSession(
"user123",
"张三",
"token_abc123"
);
System.out.println("原始Session: " + session);
// 序列化到文件
String filename = "session.ser";
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(filename))) {
oos.writeObject(session);
System.out.println("序列化成功!");
} catch (IOException e) {
System.out.println("序列化失败: " + e.getMessage());
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(filename))) {
UserSession restored = (UserSession) ois.readObject();
System.out.println("反序列化Session: " + restored);
System.out.println("Token(被transient忽略): " + restored.getToken());
} catch (IOException | ClassNotFoundException e) {
System.out.println("反序列化失败: " + e.getMessage());
}
}
}
class UserSession implements Serializable {
private static final long serialVersionUID = 1L;
private String userId;
private String username;
private transient String token; // 不序列化
private transient Date accessTime; // 不序列化
public UserSession(String userId, String username, String token) {
this.userId = userId;
this.username = username;
this.token = token;
this.accessTime = new Date();
}
public String getToken() {
return token; // 反序列化后为null
}
@Override
public String toString() {
return "UserSession{" +
"userId='" + userId + '\'' +
", username='" + username + '\'' +
", token='" + token + '\'' +
'}';
}
}
序列化注意事项
性能问题:序列化速度较慢,注意使用场景
安全性:敏感数据不要序列化,或使用加密
版本兼容性:修改类后保持serialVersionUID一致
单例模式:序列化可能破坏单例,需要实现readResolve
小结
- 序列化将对象转换为字节序列,用于持久化和传输
- 类必须实现Serializable接口才能序列化
- transient修饰的字段不参与序列化,反序列化后为默认值
- serialVersionUID用于版本兼容性验证
- 静态字段不参与序列化
- 敏感信息不要序列化,或使用自定义序列化