Java反序列化异常:className字段未正常反序列化的问题求助
OpenJDK 17反序列化final字段className未恢复序列化值的原因解析
问题现象
反序列化SerialTest实例时,millisThen、dateTimeThen两个final字段能正常从序列化流中恢复原值,但className字段始终使用当前代码中定义的初始值,而非序列化时的取值:
- 第一次运行(
className = SerialTest.class.getName())序列化实例后,输出中className显示为SerialTest; - 修改代码将
className改为"Why this?!"后再次运行(仅反序列化),输出中className显示为Why this?!,而非序列化时的SerialTest。
核心原因:Java序列化对编译期常量final字段的特殊处理
Java序列化机制会区分编译期常量表达式初始化的final字段和运行时初始化的final字段:
- 编译期常量final字段:如果final字段是通过编译期常量表达式(如直接字符串字面量、基本类型常量等)初始化的,Java会将其视为常量,序列化时不会把该字段的值写入序列化流;反序列化时,直接使用当前类中定义的常量值,完全忽略流中可能存在的旧值。
- 运行时初始化的final字段:如果final字段是通过运行时计算(如调用方法、动态生成值等)初始化的,序列化时会将字段值写入流中,反序列化时会从流中恢复原值,覆盖类中的初始定义。
对应到你的代码:
millisThen = System.currentTimeMillis()、dateTimeThen = LocalTime.now().toString():都是运行时调用方法生成的值,属于运行时初始化的final字段,因此会被正常序列化/反序列化。- 当你把
className改为"Why this?!"时,这是一个编译期字符串常量,序列化机制会跳过该字段的反序列化逻辑,直接使用当前类中的常量值;而第一次序列化时,className = SerialTest.class.getName()是运行时调用方法生成的值,虽然会被写入流,但反序列化时因为当前类的className是编译期常量,所以流中的旧值被忽略。
验证逻辑
如果将第二次的className改为运行时初始化的形式,比如:
public final String className = new String("Why this?!");
此时className不再是编译期常量,反序列化时就会从流中读取第一次序列化时的SerialTest值,而不是当前的Why this?!。
解决方案
如果需要让className字段在反序列化时恢复序列化时的原值,需确保该字段不是通过编译期常量表达式初始化:
- 改用运行时初始化方式(比如调用方法、new对象等);
- 若必须使用常量形式,可考虑将字段改为非final,或通过
readObject方法手动处理反序列化逻辑。
内容的提问来源于stack exchange,提问作者Dave The Dane
相关产品推荐
相关产品推荐

