登录
首页 >  文章 >  java教程

Java序列化与反序列化详解

时间:2026-05-29 18:50:56 490浏览 收藏

Java序列化远不止是“对象变字节流”的简单操作,它深度绑定类结构、继承关系与字段状态,却暗藏NotSerializableException、InvalidClassException等运行时陷阱;transient修饰符和自定义read/writeObject是绕过不可序列化资源的必备技巧,而显式声明serialVersionUID更是保障版本兼容的生命线;更关键的是,JDK原生序列化因格式封闭、安全风险高、跨语言不支持、升级脆弱且性能落后,在真实生产环境中已被JSON、Protobuf、Kryo等方案全面替代——真正考验工程师的,不是如何序列化,而是清醒判断何时绝不能序列化。

什么是Java中的序列化与反序列化_对象状态的持久化与网络传输

Java序列化就是把对象变成字节流,反序列化就是把字节流变回对象——它不是“存数据”那么简单,而是完整保存对象的类结构、字段值、甚至继承链信息。

ObjectOutputStream.writeOject() 为什么一调就抛 NotSerializableException?

这个异常不是因为你忘了写代码,而是 JVM 在运行时发现某个字段指向了不可序列化的类型。常见原因包括:

  • 类没实现 Serializable 接口(最基础要求)
  • 某个实例变量是 ThreadSocketConnection 等系统资源类,它们本身没实现接口
  • 用了匿名内部类或 Lambda 表达式,其隐式引用了外部类,而外部类未序列化
  • 父类没实现 Serializable,子类虽实现了,但父类字段不会被自动序列化(除非父类也实现)

解决办法很简单:给不该序列化的字段加 transient 修饰符;对必须保留状态的资源类,手动在 writeObject() / readObject() 中处理。

serialVersionUID 不显式声明会怎样?

不写,JVM 会按类名、方法签名、字段等自动生成一个哈希值。一旦你改了字段名、删了方法、加了新字段,哪怕只是加个注释,这个值就变了——反序列化时直接报 InvalidClassException: class invalid for deserialization

所以建议始终显式定义:

private static final long serialVersionUID = 1L;

注意:1L 可以,但上线项目最好用 IDE 自动生成的 64 位长整数(如 -8725093427384320001L),避免多人开发时手误导致版本错乱。

为什么生产环境慎用 JDK 原生序列化?

它快、简单、不用额外依赖,但有硬伤:

  • 字节流格式私有且不跨语言——Python 或 Go 服务根本读不了 .ser 文件
  • 反序列化过程会执行类的静态初始化块和构造器,存在反序列化 gadget 风险(比如 Apache Commons Collections 利用链)
  • 类结构稍有变动就失败,升级兼容性差;没有字段默认值机制,新增字段会导致旧客户端反序列化失败
  • 性能上比 JSON/Protobuf 差不少,尤其字段多、嵌套深时,二进制体积反而更大

真实场景中,RPC(如 Dubbo)默认已切到 Hessian 或 Kryo;缓存(Redis)基本都用 JSON;只有本地临时文件或测试 demo 才用 ObjectOutputStream

真正难的从来不是“怎么写”,而是“什么时候不该写”——比如把 HttpServletRequest 或 Spring 的 ApplicationContext 往里塞,或者以为加个 Serializable 就万事大吉,结果上线后某次字段重命名,凌晨三点收到告警。

以上就是《Java序列化与反序列化详解》的详细内容,更多关于的资料请关注golang学习网公众号!

资料下载
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>