根本原因是类中存在不可序列化的成员字段,如thread、socket、匿名内部类引用或未实现serializable的第三方类;应使用transient标记非序列化字段、检查所有字段可序列化性、避免lambda捕获this,并自定义writeobject/readobject处理非序列化资源。

为什么加了 Serializable 还抛 NotSerializableException
根本原因不是没加接口,而是类里有不可序列化的成员——比如 Thread、Socket、匿名内部类引用外部非序列化对象,或者用了未实现 Serializable 的第三方类字段。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 用
transient显式标记不该被序列化的字段(如缓存、连接句柄) - 检查所有非基本类型字段是否都实现了
Serializable;尤其注意ArrayList里的元素类型是否可序列化 - 避免在
Serializable类中持有lambda表达式或匿名类实例,它们会隐式捕获this引用 - 如果必须存非序列化资源,改用
writeObject()/readObject()自定义序列化逻辑
serialVersionUID 不写会怎样?什么时候必须手动指定
不写时 JVM 会基于类结构自动生成一个 serialVersionUID,但只要类稍作修改(增删字段、改访问修饰符、甚至换 JDK 版本),生成值就可能变化,导致反序列化时抛 InvalidClassException:class incompatible with stream。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 所有实现
Serializable的类,第一行就加上private static final long serialVersionUID = 1L; - 版本升级需兼容旧数据时,只增加
transient字段或提供默认值,serialVersionUID保持不变 - 若字段语义变更(如
int age改为LocalDateTime birthDate),应更新serialVersionUID并重写readObject()做迁移 - IDEA 或
serialver命令可生成初始值,但别直接用它生成的长数字——可读性差且无意义
用 ObjectOutputStream 写文件后,为什么别的语言读不了
Java 默认序列化是二进制私有协议,含类名、字段描述、JVM 版本等元信息,其他语言基本无法解析。这不是 bug,是设计使然。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 跨语言场景下,彻底放弃
ObjectOutputStream,改用 JSON(jackson)、Protobuf(protobuf-java)或 Avro - 即使同是 Java,不同 JDK 小版本间也可能存在兼容性问题(如 JDK 17 序列化对象在 JDK 8 反序列化失败)
- 若必须用原生序列化,确保两端 JDK 版本一致,并严格管理
serialVersionUID和字段变更 - 不要把序列化字节流当通用存储格式——它不是为持久化设计的,只是临时进程间传递的权宜之计
反序列化时怎么防止恶意代码执行
ObjectInputStream 在读取字节流时会自动调用目标类的 readObject(),而攻击者可构造恶意字节流触发任意类的静态初始化块或 readObject() 中的危险逻辑(如 Runtime.exec())。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 永远不要对不可信来源的字节流调用
ObjectInputStream.readObject() - 使用
ObjectInputStream.resolveClass()重写,白名单限制只允许加载指定类(如MyData.class) - 更推荐方案:用
ValidationInputFilter(JDK 9+)或在ObjectInputStream构造后立即设置过滤器:ois.setObjectInputFilter(...) - Spring 等框架默认禁用反序列化,除非显式配置;生产环境应视
ObjectInputStream为高危 API
序列化看着简单,但字段生命周期、JVM 版本、安全边界、跨语言需求这些点一碰就容易出问题。最常被忽略的是:它从来就不是个通用数据交换机制,只是 Java 生态内部一个带坑的快捷键。










