Java 序列化不只是写个 implements Serializable:面试、坑与真相

0 阅读3分钟

写 Java 时难免遇到序列化,但很多人提到它就打哈欠。直到面试官盯着你问:“Serializable 为啥要 serialVersionUID?transient 有啥用?”——这时候你才觉得,这东西真不是摆设。去网络查了半天,发现大家都讲得挺公式的,我这里想从真正在项目和面试里踩过的坑来说说。

想象一下,你有一个对象,某一天要把它“拿出来存档”,可能是写文件、可能是发给另外一台服务器。序列化(serialize)在 Java 里,就是把对象变成一串二进制,之后能再从这串二进制“复活”出对象。反过来就是反序列化(deserialize)了。

这听起来挺直白,但坑在细节。

我拿一个简单的类举例,先看看怎么把它写进文件、再读回来:

import java.io.*;

// 这是个能被序列化的类
class Account implements Serializable {
    private static final long serialVersionUID = 42L; // 面试常问
    private String username;
    private transient String secret; // 不想序列化的字段

    public Account(String username, String secret) {
        this.username = username;
        this.secret = secret;
    }
    public String toString() {
        return username + " | secret=" + secret;
    }
}

public class SerializeDemo {
    public static void main(String[] args) throws Exception {
        Account acct = new Account("alice", "123456");

        // 写出去
        try (ObjectOutputStream out =
                new ObjectOutputStream(new FileOutputStream("acct.bin"))) {
            out.writeObject(acct);
        }

        // 读回来
        try (ObjectInputStream in =
                new ObjectInputStream(new FileInputStream("acct.bin"))) {
            Account recovered = (Account) in.readObject();
            System.out.println("Loaded: " + recovered);
        }
    }
}

运行完你可能会发现,打印出来的 secretnull。原因是我们把它 transient了——意思是“别管它,不参与序列化过程”。这在面试里是个常考点:敏感信息(比如密码)要别序列化,否则写到磁盘谁都能读出。

有些面试官会专门问:

“为什么要写 serialVersionUID?不写会怎样?”

答案不难抓住:
序列化机制内部会根据类结构生成一个默认版本号。如果你序列化以后改了类结构(比如新增字段),再去反序列化旧数据,很可能抛出 InvalidClassException。主动写了 serialVersionUID,就是自己把版本号钉死,这让旧数据还能顺利反序列化(当然要你自己判断字段语义是否兼容)。

还有一些更细碎但面试常问的实战:

▪ 字段有非 Serializable 类型怎么办?

如果你的类里有字段引用了一个不支持序列化的对象,会抛 NotSerializableException
解决办法也不复杂:

  • 要么把该字段标记成 transient
  • 要么让那个字段本身也实现 Serializable

这在面试里出现得挺高频,特别是你写了一个包含第三方库对象的类去序列化的时候。

▪ Externalizable?为什么有人提它

有些面试会往深走一步,问你有没有比 Serializable 更灵活的方式。
答案是 Externalizable:它需要你自己写出序列化和反序列化逻辑,更费事,但能更精细控制保存内容。对大对象、多版本协议的系统有用。

▪ 序列化不只是写文件

尽管示例里是写文件,但其实场景更广:

  • 分布式缓存(把对象放进 Redis 之类的),
  • JVM 之间对象传输,
  • Web session 在集群间复制(很多 servlet 容器会自动序列化 session 对象)。
    面试问到这些场景时,你能具体说出来比只讲概念要有含金量。

写到这儿你可能会想,“这不就是把对象扔到字节流然后再捡回来吗?”对,就是这样,但为什么有那么多人说这东西容易错、不安全、被现代微服务架构嫌弃?因为 Java 自带的序列化格式是 JVM 专有的,不容易在不同语言/服务间共享,而且一不小心的反序列化确实有安全隐患——这也是大公司喜欢用 JSON、Protobuf 这些替代方案的原因。

如果你在面试里说:“我知道怎么写 Serializable 并且了解 transientserialVersionUID,还能简单讲讲 Externalizable 和安全性问题”,往往比背一堆定义更能打动对方。因为面试不只是问你怎么写代码,更想知道你对“为什么这么做”和“实际风险”的理解。