💡 类比:
序列化 = 把家具拆成零件打包(方便搬家)
反序列化 = 到新家后,按说明书重新组装家具
持久化对象:把对象保存到文件、数据库(如 Redis、Hibernate)。
网络传输:通过网络发送对象(如 RPC、RMI、HTTP API 传对象)。
跨 JVM 通信:不同 Java 进程之间传递对象状态。
缓存:将对象临时存入磁盘或内存缓存。
Serializable 接口Java 中,只要让类实现 java.io.Serializable 接口,它的对象就可以被序列化。
import java.io.*;
import java.util.Date;
// 实现 Serializable 接口(标记接口,无方法)
public class Person implements Serializable {
private static final long serialVersionUID = 1L; // 推荐显式声明
private String name;
private int age;
private transient String password; // 不参与序列化
private Date createTime = new Date();
public Person(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age +
", password='" + password + "', createTime=" + createTime + "}";
}
}public class SerializeDemo {
public static void main(String[] args) throws Exception {
Person person = new Person("Alice", 30, "secret123");
// 序列化到文件
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("person.ser"))) {
oos.writeObject(person);
System.out.println("✅ 对象已序列化到 person.ser");
}
}
}public class DeserializeDemo {
public static void main(String[] args) throws Exception {
// 从文件反序列化
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("person.ser"))) {
Person person = (Person) ois.readObject();
System.out.println("✅ 反序列化成功: " + person);
}
}
}✅ 对象已序列化到 person.ser
✅ 反序列化成功: Person{name='Alice', age=30, password='null', createTime=Mon Oct 21 12:30:45 CST 2025}🔍 注意:
password 是 transient,所以反序列化后为 null
createTime 被正常恢复(因为 Date 也实现了 Serializable)
serialVersionUID 的作用你序列化了一个 Person 对象到磁盘;
后来你给 Person 类增加了一个字段;
再反序列化时,JVM 会报错:InvalidClassException
JVM 会为每个可序列化类计算一个默认的 serialVersionUID(基于类名、字段、方法等)。类结构一变,UID 就变,导致不兼容。
显式声明 serialVersionUID:
private static final long serialVersionUID = 1L; // 固定值只要你不主动修改这个值,即使类结构变化(如加字段),也能兼容反序列化(新字段为默认值)。
✅ 最佳实践:所有实现 Serializable 的类都应显式声明 serialVersionUID
⚠️ 如果父类没实现 Serializable,反序列化时会调用父类的无参构造器来初始化父类部分。
有时默认序列化不够用(比如要加密、压缩、兼容旧格式),可通过以下方法自定义:
writeObject() / readObject()private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // 先序列化默认字段
// 自定义逻辑:比如加密 password 再写入
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // 先反序列化默认字段
// 自定义逻辑:解密 password
}writeReplace() / readResolve()用于替换被序列化的对象(常用于单例模式防止破坏):
// 防止单例被反序列化破坏
private Object readResolve() {
return INSTANCE; // 返回唯一的单例实例
}如果密码、密钥等字段未标记 transient,会被写入磁盘或网络。
解决方案:敏感字段必须 transient,或自定义序列化逻辑。
攻击者可构造恶意字节流,在反序列化时执行任意代码。
著名案例:Apache Commons Collections、Fastjson 漏洞。
防御措施:
避免反序列化不可信数据;
使用 ObjectInputValidation 校验;
升级到安全库(如 Jackson、Gson);
Java 9+ 可使用 ObjectInputFilter 限制可反序列化的类。
忘记写 serialVersionUID,导致升级后无法反序列化。
解决方案:始终显式声明 serialVersionUID。
虽然 Java 原生序列化简单,但存在性能差、体积大、跨语言不兼容等问题。现代项目更常用:
✅ 建议:除非必须用 Java 原生序列化(如 RMI),否则优先选 JSON 或 Protobuf。
提示:如遇链接失效,请在评论区留言反馈