小柘小筑站

序列化和反序列化

2025/11/05
9
0

一、什么是序列化和反序列化?

概念

说明

序列化(Serialization)

Java 对象 转换为 可存储或可传输的字节流(byte stream) 的过程。

反序列化(Deserialization)

字节流 重新恢复为 Java 对象 的过程。

💡 类比:

  • 序列化 = 把家具拆成零件打包(方便搬家)

  • 反序列化 = 到新家后,按说明书重新组装家具


二、为什么需要序列化?

  1. 持久化对象:把对象保存到文件、数据库(如 Redis、Hibernate)。

  2. 网络传输:通过网络发送对象(如 RPC、RMI、HTTP API 传对象)。

  3. 跨 JVM 通信:不同 Java 进程之间传递对象状态。

  4. 缓存:将对象临时存入磁盘或内存缓存。


三、如何实现序列化?——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 + "}";
    }
}

🔑 关键点说明:

要素

说明

implements Serializable

标记该类可序列化(必须

serialVersionUID

序列化版本号,用于校验类是否兼容(强烈建议显式声明

transient

标记字段不参与序列化(如密码、临时缓存)


四、序列化 & 反序列化代码演示

1. 序列化:对象 → 文件

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");
        }
    }
}

2. 反序列化:文件 → 对象

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}

🔍 注意:

  • passwordtransient,所以反序列化后为 null

  • createTime 被正常恢复(因为 Date 也实现了 Serializable


五、深入:serialVersionUID 的作用

问题场景:

  • 你序列化了一个 Person 对象到磁盘;

  • 后来你给 Person增加了一个字段

  • 再反序列化时,JVM 会报错:InvalidClassException

原因:

JVM 会为每个可序列化类计算一个默认的 serialVersionUID(基于类名、字段、方法等)。类结构一变,UID 就变,导致不兼容。

解决方案:

显式声明 serialVersionUID

private static final long serialVersionUID = 1L; // 固定值
  • 只要你不主动修改这个值,即使类结构变化(如加字段),也能兼容反序列化(新字段为默认值)。

✅ 最佳实践:所有实现 Serializable 的类都应显式声明 serialVersionUID


六、哪些内容会被序列化?

成员类型

是否序列化

说明

transient 的实例字段

包括 private 字段

transient 字段

显式排除

静态字段(static

属于类,不属于对象

方法、构造器

不存储状态

父类字段

✅(如果父类也实现了 Serializable

否则父类字段不会被序列化

⚠️ 如果父类没实现 Serializable,反序列化时会调用父类的无参构造器来初始化父类部分。


七、自定义序列化行为(高级)

有时默认序列化不够用(比如要加密、压缩、兼容旧格式),可通过以下方法自定义:

1. writeObject() / readObject()

private void writeObject(ObjectOutputStream out) throws IOException {
    out.defaultWriteObject(); // 先序列化默认字段
    // 自定义逻辑:比如加密 password 再写入
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject(); // 先反序列化默认字段
    // 自定义逻辑:解密 password
}

2. writeReplace() / readResolve()

用于替换被序列化的对象(常用于单例模式防止破坏):

// 防止单例被反序列化破坏
private Object readResolve() {
    return INSTANCE; // 返回唯一的单例实例
}

八、常见陷阱与安全风险

❌ 1. 敏感信息泄露

  • 如果密码、密钥等字段未标记 transient,会被写入磁盘或网络。

  • 解决方案:敏感字段必须 transient,或自定义序列化逻辑。

❌ 2. 反序列化漏洞(严重安全问题!)

  • 攻击者可构造恶意字节流,在反序列化时执行任意代码。

  • 著名案例:Apache Commons Collections、Fastjson 漏洞。

  • 防御措施

  • 避免反序列化不可信数据;

  • 使用 ObjectInputValidation 校验;

  • 升级到安全库(如 Jackson、Gson);

  • Java 9+ 可使用 ObjectInputFilter 限制可反序列化的类。

❌ 3. 类版本不兼容

  • 忘记写 serialVersionUID,导致升级后无法反序列化。

  • 解决方案:始终显式声明 serialVersionUID


九、替代方案(现代开发推荐)

虽然 Java 原生序列化简单,但存在性能差、体积大、跨语言不兼容等问题。现代项目更常用:

方案

特点

JSON(Jackson / Gson)

人类可读、跨语言、轻量

Protocol Buffers(Protobuf)

高效、紧凑、强类型

Kryo

高性能 Java 专用序列化库

✅ 建议:除非必须用 Java 原生序列化(如 RMI),否则优先选 JSON 或 Protobuf。


十、总结

项目

说明

目的

对象 ↔ 字节流(持久化/传输)

实现方式

类实现 Serializable 接口

关键字段

serialVersionUID(必须显式声明!)

排除字段

transient 修饰

安全警告

反序列化不可信数据 = 高危操作!

现代替代

JSON、Protobuf 更安全高效

资源下载

提示:如遇链接失效,请在评论区留言反馈