我对序列化的真实理解

其实对于初学者来说,序列化其实是不好理解的,然后我也是属于其中的一员,感觉始终不是很理解这些东西,然后通过询问ai,我有了自己的理解,如果有不对欢迎指出谢谢。

我对序列化的理解就一句话:它的作用就是把对象转成二进制,用来传输和存储。

它就是为了让我们传输和存储java对象的,所以不要觉得它有多高大上。然后我们自己接触的很少,因为一般都是框架帮我们做了,虽然我们没做,这就跟反射一样,但是它其实一直在发生。

比如,我的前端发来一个 JSON 请求,我 Controller 方法里那个被 @RequestBody 修饰的 Java 对象,是怎么"变"出来的?这是反序列化 。Spring Boot 默认用了 Jackson 框架,替我把 JSON 字符串转成了 Java 对象。我返回一个对象给前端,那个对象又是怎么变成 JSON 字符串通过网络传回去的?这是序列化。同样是 Jackson,默默地在背后替我做了。

还有我用 RabbitMQ 发送消息,消息体里放着 Java 对象。RabbitMQ 本身不认 Java 对象,它只认二进制数据。所以,我的对象在被发送出去之前,必须被序列化 成字节流;消费者收到字节流后,也必须反序列化回 Java 对象。这一切,也是框架在背后默默完成的。

所以,"为了传输和存储" 这个理解依然是核心。但它不是空洞的理论,而是在我项目里时刻都在发生的事实。只是因为这些脏都被框架(比如Jackson)、工具(比如RabbitMQ)默默地做了,才感觉它好像离我们很远。

在理解上面的基础上在理解深层次的东西。

如果我的对象结构变了怎么办?

比如,我的第一个版本里,User 类只有 nameage。业务升级,我加了一个 email 字段。新的对象当然没问题,但问题是,我已经存进 Redis 的那些旧数据怎么办?它们只有 nameage,现在要反序列化成带有 email 的新 User 对象,程序会不会崩?

如果是Jackson我加了一个 email 字段。当我用新的 User 类去反序列化这条旧 JSON 数据时,Jackson 会淡定地发现,JSON 里没有 email 这个字段。它不会崩溃,而是会直接给新字段设一个默认值,比如 null。反过来,如果 JSON 里多了一个 phone 字段,但我的 User 类里没有,Jackson 也只会忽略掉它,不会报错。你少了,我给你补个默认值;你多了,我就当没看见。

如果是java序列化的话这就是 serialVersionUID 要解决的问题。如果我显式定义了这个 ID,我加一个字段,ID 不变,JVM 就认为它们还是同一个类。反序列化时,它会把新增的 email 字段设为 null,然后继续正常工作。这样,新老版本的数据就兼容了,旧数据不会变成"僵尸"。如果没有显示定义,会根据数据结构来生成这个uid,所以一旦结构变化,那么旧数据无法反序列化因为会认为他们不是一个东西。

有些东西我不想被序列化怎么办?

这是真实的安全需求。我的 User 对象里肯定有 password 字段。如果我直接把 User 对象序列化成 JSON 返回给前端,密码就泄露了。

解决这个问题也很简单。如果是 Java 原生序列化,就用 transient 关键字标记这个字段。如果是 JSON 序列化(就像我项目里用的 Jackson),就用 @JsonIgnore 注解。被打上标记的字段,在序列化时会被直接跳过,反序列化出来就是默认值(null0false)。

序列化的多种方式

java 原生序列化、JSON、Protobuf...这些都是在不同场景下的不同选择。Java 原生序列化兼容性最好,但生成的数据太大,也不跨语言;JSON 跨语言、可读性好,是现在用得最多的,我项目里也是用这个;Protobuf 性能高、数据最小,但可读性差。

所以,我后来对序列化的理解更新为 :序列化不仅是一种数据转换的技术,更是一套完整的"对象进化兼容方案 "。它通过 serialVersionUID 解决了版本兼容问题,通过 transient / @JsonIgnore 解决了隐私保护问题,并通过多种实现框架提供了灵活的控制权。