父类字段在子类反序列化时“被覆盖”实为伪覆盖:一是父类未实现serializable,反序列化时字段重置为默认值;二是父子类同名字段导致序列化器重复赋值而丢失。

父类字段在子类反序列化时被“覆盖”,本质不是覆盖,而是字段初始化逻辑冲突或重复赋值导致的值丢失——常见于两种典型场景:一是父类未实现 Serializable,反序列化时父类字段被重置为默认值(null、0、false),看似“被覆盖”;二是父子类定义了同名字段(如都声明了 traceId),Dubbo 或某些序列化框架按字段名逐层赋值,子类字段值写入后又因反射顺序或 setter 调用,意外清空父类同名字段。
确认是否属于“伪覆盖”:父类未实现 Serializable
这是最常被误认为“覆盖”的情况。Java 原生序列化规定:只要父类没实现 Serializable,它声明的所有实例字段都不会写入字节流。反序列化时,JVM 会调用父类无参构造器重建父类部分,字段全部恢复为默认值。
- 例如:
User extends Entity<long></long>,若Entity没实现Serializable,则id字段反序列化后必为null,不是被子类覆盖,而是根本没保存过 - 验证方式:打印反序列化前后对象的
getClass().getSuperclass(),再检查该父类是否 implementsSerializable - 修复方式:给父类加上
implements Serializable,并显式声明private static final long serialVersionUID
排查真实字段重复定义问题
当父子类中都声明了同名字段(尤其 private 字段),某些序列化框架(如 Dubbo 的 JavaSerializer)会在反序列化过程中分别处理子类和父类字段。由于字段名相同,可能先设子类字段,再设父类字段(或反之),而父类 setter 可能被忽略,最终只保留最后一次赋值的结果。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 典型表现:消费者端设置了
traceId = "abc",提供者端收到的对象中traceId为null或空字符串 - 根本原因:子类里也写了
private String traceId;,造成字段隐藏(field hiding),序列化器无法区分归属,赋值逻辑混乱 - 解决办法:删除子类中与父类同名的字段,统一由父类定义和维护;若必须扩展,改用不同字段名(如
subTraceId)或通过组合替代继承
使用自定义序列化规避继承干扰
当父类不可修改(如第三方 SDK 中的类),又存在字段需保留时,子类可通过重写 writeObject 和 readObject 手动控制父类字段的读写。
-
writeObject中先调用defaultWriteObject()序列化子类字段,再用out.writeXXX()显式写出父类字段值 -
readObject中先调用defaultReadObject(),再用in.readXXX()读回并调用父类 setter 或反射赋值 - 前提:父类必须有可访问的无参构造器,否则反序列化直接抛
InstantiationException
多态 JSON 场景下父类标识字段为空的误区
这不是 Java 原生序列化问题,但在 Spring Boot + Jackson 场景中常被混为一谈:父类定义了 type 字段用于多态识别,但反序列化后该字段为 null。这并非覆盖,而是 Jackson 默认不把类型信息写入业务字段。
- 正确做法是配置
@JsonTypeInfo(include = As.EXISTING_PROPERTY, visible = true),让 Jackson 既用type字段做类型路由,也把它当作普通字段赋值 - 字段访问权限建议设为
protected,避免public字段引发意外覆盖或跳过 setter
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










