Java 序列化

序列化(Serialization)是把 Java 对象转换为字节序列并写入文件、数据库或网络的过程,反序列化则是把字节序列还原为对象。有了序列化,对象才能落盘、跨进程传输,它是 RPC、缓存、消息队列、Session 持久化等技术的底层基石,Java 后端开发者必须掌握。

序列化的概念与用途

一句话概括:序列化 = 对象 → 字节流;反序列化 = 字节流 → 对象。典型用途:

  • 对象存入磁盘,程序重启后仍能恢复;
  • 对象在网络上传输(Socket、HTTP、RPC);
  • 对象存入 Redis、消息队列等中间件;
  • 借助"先序列化再反序列化"实现深拷贝。

实现 Serializable 接口

一个类只需实现标记接口 java.io.Serializable 即可被序列化,该接口本身没有任何方法:

import java.io.Serializable;

public class User implements Serializable {
    private String name;
    private int age;
    // 构造方法、getter/setter、toString 省略
}

未实现该接口就写入对象流,会抛出 java.io.NotSerializableException

serialVersionUID 的作用

它是类的"版本号",反序列化时 JVM 用它校验字节流与当前类是否兼容:

public class User implements Serializable {
    private static final long serialVersionUID = 1L;  // 显式声明,推荐
    private String name;
    private int age;
}

若不声明,JVM 会按类结构自动生成;一旦类增删字段导致编号变化,旧数据反序列化将抛 InvalidClassException。显式固定编号后,字段小幅升级也能兼容旧数据。

序列化到文件完整流程

把 User 写成非 public 类与主类同放一个文件,即可直接运行完整程序:

import java.io.*;

class User implements Serializable {
    private static final long serialVersionUID = 1L;
    String name;
    int age;
    User(String n, int a) { name = n; age = a; }
    public String toString() { return "User{name=" + name + ", age=" + age + "}"; }
}

public class SerializeDemo {
    public static void main(String[] args) throws Exception {
        User u = new User("张三", 25);
        // 第一步:写对象到文件
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("user.dat"))) {
            oos.writeObject(u);
        }
        // 第二步:从文件读回对象
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("user.dat"))) {
            User back = (User) ois.readObject();
            System.out.println(back);  // 输出:User{name=张三, age=25}
        }
    }
}

writeObject 负责序列化、readObject 负责反序列化;两者都实现 AutoCloseable,配合 try-with-resources 自动关闭文件句柄。

transient 字段

不想落盘的字段用 transient 修饰,序列化时被跳过,反序列化时取默认值:

public class User implements Serializable {
    private String name;
    private transient String password;  // 密码不参与序列化
    private transient int age;          // 反序列化后 age 为 0
}

反序列化安全提示

  • 反序列化会执行 readObject,恶意构造的数据可能触发远程代码执行,切勿反序列化不可信来源的数据
  • 密码、令牌等敏感信息加 transient 或加密后再存,防止明文泄漏。
  • 线上类结构变更前先确认 serialVersionUID 与旧数据一致,再发布升级。

小结:序列化让对象"存得下、传得走"——实现 Serializable、声明 serialVersionUID、用 ObjectOutputStream/ObjectInputStream 完成读写,敏感字段用 transient 保护。

笔记加载中…