所有父类必须实现serializable,否则writeobject()抛notserializableexception;serialversionuid需显式声明并按兼容性规则更新;transient和static字段继承生效,构造函数不参与序列化。

继承链中所有父类都必须实现 Serializable
不满足这点,ObjectOutputStream 在调用 writeObject() 时会直接抛出 NotSerializableException,哪怕子类已实现接口。Java 序列化是递归遍历整个对象图的,父类字段(包括 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<byte></byte>、嵌套 Map),否则序列化体积和耗时会指数增长。
- 父类中敏感字段(如密码、token)用
transient是安全做法,子类无需重复声明 -
static字段无论在哪一层定义,都不会进入序列化流——这点常被误认为“子类能覆盖父类 static 行为”,实际是 JVM 层面跳过 - 若需在反序列化后初始化某些状态(比如重建父类中的缓存 map),可实现
readObject(ObjectInputStream)方法,在其中手动赋值
构造函数不会被调用,父类无参构造不是必须的
Java 默认序列化机制不通过构造函数重建对象,而是用 JVM 特殊机制直接分配内存并填充字段值。因此父类是否提供无参构造函数,不影响序列化/反序列化流程——但有个关键例外:父类未实现 Serializable 时,JVM 会尝试调用其无参构造器来初始化字段,此时才真正需要它。
容易踩的坑:在父类中写了带参构造器、又删了默认无参构造,同时父类没实现 Serializable,结果反序列化失败报 InvalidClassException: no valid constructor。
- 只要所有父类都实现了
Serializable,就完全不用管构造器是否存在或是否 public - 如果某层父类确实不能改(如
java.util.Date子类),而你又必须序列化,那就得让该子类实现Externalizable,自己控制 writeExternal/readExternal 逻辑 - 注意:子类中定义的
writeObject/readObject方法,必须显式调用defaultWriteObject()/defaultReadObject()才能保证父类字段被正常处理
serialVersionUID 协调和字段兼容性判断越难;最容易被忽略的是父类来自外部包却未实现 Serializable,这时连编译都过不去,更别说运行时序列化了。











