Java泛型类的对象序列化与反序列化原理探究
Java泛型类型擦除后,序列化与反序列化如何保留类型信息?
我知道Java编译器会对泛型类执行类型擦除,将类型参数的类型信息替换为Object或其上限。但有个疑问:序列化及后续反序列化时,原始类型是怎么恢复的?
比如定义如下泛型类:
public class TestGeneric<T extends Serializable> implements Serializable { T value; public TestGeneric(T value) { this.value = value; } public T getValue() { return this.value; } }
按照类型擦除的规则,我认为JVM运行时会把它视为:
public class TestGeneric implements Serializable { Serializable value; public TestGeneric(Serializable value) { this.value = value; } public Serializable getValue() { return this.value; } }
但执行以下代码时,第二次运行(反序列化阶段)会抛出类型转换异常:
public static void main(String argv[]) { File newFile = new File("./test.txt"); try { if (!newFile.exists()) { TestGeneric<Double> testGeneric = new TestGeneric<>(0.2d); try (FileOutputStream fos = new FileOutputStream(newFile); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(testGeneric); } } else { try (FileInputStream fis = new FileInputStream(newFile); ObjectInputStream ois = new ObjectInputStream(fis)) { try { TestGeneric<Boolean> genericRead = (TestGeneric<Boolean>) ois.readObject(); Boolean isNotABoolean = genericRead.getValue(); System.out.println(isNotABoolean); } catch (ClassNotFoundException e) { System.err.println("Class not found!"); } } } } catch(IOException e) { System.err.println("IO Exception occurred, reason: " + e.getMessage()); } }
报错信息:
Exception in thread "main" java.lang.ClassCastException: class java.lang.Double cannot be cast to class java.lang.Boolean (java.lang.Double and java.lang.Boolean are in module java.base of loader 'bootstrap')
这看起来就算经过类型擦除,JVM运行时还是知道字段的确切类型,这是怎么实现的?
核心原因:类型擦除针对泛型类定义,而非对象实例的运行时类型
- 类型擦除的作用范围
类型擦除是Java泛型的编译期特性,它只修改泛型类的字节码定义——把类里的泛型参数T替换为它的上限Serializable,但这并不影响对象实例在运行时的实际类型信息。
比如你创建TestGeneric<Double>实例时,它的value字段实际存储的是Double对象,这个对象的运行时类型是明确的,不会因为类定义被擦除而改变。
- 序列化保存的是对象的运行时状态
ObjectOutputStream在序列化对象时,会把对象的完整运行时类型信息(包括每个字段的实际类型)写入到序列化流中,而不是擦除后的类定义信息。
对于上面的例子,序列化TestGeneric<Double>实例时,流里会记录value字段的实际类型是Double,而不是Serializable。
- 反序列化的类型重建与隐式转换
反序列化时,ObjectInputStream会根据流里的类型信息重建对象,所以value字段依然是Double类型。
代码中出现ClassCastException的原因是:
- 你做了一个未经检查的强制转换:
(TestGeneric<Boolean>) ois.readObject()——因为类型擦除,编译器无法在编译时验证这个转换的合法性,只会给出警告。 - 当你调用
genericRead.getValue()时,编译器会插入一个隐式类型转换:把擦除后的返回值Serializable强制转为Boolean。但此时value实际是Double对象,所以运行时抛出类型转换异常。
简单来说:泛型类的定义被擦除,但对象实例的实际类型信息在运行时是存在的,序列化会完整保存这些信息,反序列化时会根据这些信息重建真实类型的对象。
内容的提问来源于stack exchange,提问作者Sebi2020
相关产品推荐
相关产品推荐

