商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > 如何使用 ObjectOutputStream 序列化具有继承关系的复杂业务实体对象

如何使用 ObjectOutputStream 序列化具有继承关系的复杂业务实体对象

  发布于2026-07-11 阅读(0)

扫一扫,手机访问

父类不实现Serializable?writeObject()直接抛异常;serialVersionUID得自己声明,还得按兼容性规则更新;transientstatic字段的继承规则一样生效,至于构造函数——序列化里根本没它什么事。

如何使用 ObjectOutputStream 序列化具有继承关系的复杂业务实体对象

继承链中所有父类都必须实现 Serializable

如果父类没实现Serializable,那ObjectOutputStream一调用writeObject(),立马就给你一个NotSerializableException——哪怕子类已经实现了接口也不行。原因很简单:Ja va序列化是递归遍历整个对象图的,父类的字段(哪怕是private的)都会被写入字节流,所以父类不能缺席。

常见错误就是只给最末级子类加implements Serializable,运行时直接报错,告诉你“class com.example.Base is not serializable”。

  • 继承链上每个类(包括抽象基类、中间父类)都得显式声明implements Serializable
  • 就算父类没字段,也建议加上——否则序列化后反序列化时,JVM没法重建完整对象结构。
  • 要是父类来自第三方SDK,没法改,那就只能换方案,比如手动JSON序列化或者用Externalizable自定义逻辑。

serialVersionUID 不一致会导致 InvalidClassException

当父类或子类在后续版本中修改了字段(增删改类型)、重命名、或者没有显式定义serialVersionUID,反序列化时就可能栽跟头。JVM会基于类结构自动生成一个默认值,但只要类结构一变化,这个值也跟着变,校验就过不去了。

实际业务场景中,实体类随需求迭代,但旧数据文件还得读,这问题就特别让人头疼。

  • 所有可序列化的类(包括父类、子类)都应该显式声明private static final long serialVersionUID = 1L;
  • 升级时如果字段兼容(比如只增加transient或新增可空字段),serialVersionUID可以保持不变;如果破坏了兼容性(比如删字段、改类型),那就必须更新这个值,并配套迁移策略。
  • IDE(比如IntelliJ)能自动提示并生成基于当前结构的哈希值,但建议初期统一用1L,靠人工控制变更节奏。

transient 和 static 字段在继承中同样生效

transient修饰的字段不会被序列化,这个规则对父类字段同样适用。但要注意:子类没法“取消”父类字段的transient属性,也没法通过子类逻辑“恢复”它。

性能方面得留个心:避免在深层继承链中大量使用非transient的大对象引用(比如List、嵌套Map),否则序列化体积和耗时会指数级增长。

  • 父类中敏感字段(比如密码、token)用transient是安全做法,子类无需重复声明。
  • static字段无论在哪一层定义,都不会进入序列化流——这点常被误解成“子类能覆盖父类static行为”,实际上JVM层面就直接跳过了。
  • 如果需要在反序列化后初始化某些状态(比如重建父类中的缓存map),可以实现readObject(ObjectInputStream)方法,在其中手动赋值。

构造函数不会被调用,父类无参构造不是必须的

Ja va默认序列化机制不通过构造函数重建对象,而是用JVM特殊机制直接分配内存并填充字段值。因此父类是否提供无参构造函数,不影响序列化/反序列化流程——但有个关键例外:如果父类没实现Serializable,JVM会尝试调用它的无参构造器来初始化字段,这时候才真的需要它。

容易踩的坑:在父类中写了带参构造器、又删了默认无参构造,同时父类没实现Serializable,结果反序列化失败报InvalidClassException: no valid constructor

  • 只要所有父类都实现了Serializable,就完全不用管构造器是否存在、是否public。
  • 如果某层父类确实不能改(比如ja va.util.Date的子类),而你又必须序列化,那就得让该子类实现Externalizable,自己控制writeExternal/readExternal逻辑。
  • 注意:子类中定义的writeObject/readObject方法,必须显式调用defaultWriteObject()/defaultReadObject()才能保证父类字段被正常处理。

继承深度越大,各层serialVersionUID协调和字段兼容性判断就越难;最容易被忽略的是父类来自外部包却未实现Serializable,这时连编译都过不去,更别说运行时序列化了。

本文转载于:https://www.php.cn/faq/2382621.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注