Java泛型基类中Jackson反序列化对象失败问题排查
运行代码时抛出如下类型转换异常:
ClassCastException: class BaseResult cannot be cast to class MyResult (BaseResult and MyResult are in unnamed module of loader 'app')
异常触发点在BaseAction类的execute方法:
public abstract class BaseAction<IT extends BaseInput, RT extends BaseResult> { public RT execute(final IT inObj) throws Exception { //... TypeReference<RT> typeRef = new TypeReference<RT>() {}; return JacksonFunctions.jacksonObjectMapper.readValue(jsonString, typeRef); // 抛出ClassCastException } }
相关类定义如下:
基础类
BaseInput.java
public abstract class BaseInput { }
BaseResult.java
@Data @NoArgsConstructor public class BaseResult { @JsonProperty("ErrorCode") private int errorCode = 0; @JsonProperty("ErrorMessage") private String errorMessage = ""; }
BaseAction.java
public abstract class BaseAction<IT extends BaseInput, RT extends BaseResult> { public RT execute(final IT inObj) throws Exception { //... TypeReference<RT> typeRef = new TypeReference<RT>() {}; return JacksonFunctions.jacksonObjectMapper.readValue(jsonString, typeRef); // 抛出ClassCastException } }
实现类
MyInput.java
@Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor public class MyInput extends BaseInput { private String title; }
MyResult.java
@Data @EqualsAndHashCode(callSuper = true) @NoArgsConstructor public class MyResult extends BaseResult { @JsonProperty("Title") private String title; @JsonProperty("Description") private String description; }
MyAction.java
public class MyAction extends BaseAction<MyInput, MyResult> { }
运行代码
MyInput inObj = new MyInput(); inObj.setTitle("Galaxy"); MyAction act = new MyAction(); MyResult resObj = act.execute(inObj); // 此处抛出异常
已将MyResult作为泛型参数RT传入BaseAction的子类MyAction,但Jackson反序列化时仍将RT识别为BaseResult而非MyResult,直接在MyAction中使用TypeReference<MyResult>则可正常工作,需明确泛型基类中无法生效的原因。
核心问题是Java泛型的类型擦除机制。
编译阶段,Java会将泛型参数RT擦除为它的上界类型BaseResult,因此运行时BaseAction中的TypeReference<RT>实际等价于TypeReference<BaseResult>。Jackson基于这个类型信息反序列化时,会创建BaseResult实例,代码试图将其强制转换为MyResult,自然抛出ClassCastException。
而在子类MyAction中直接使用TypeReference<MyResult>时,该类型是具体的、未被擦除的实际类型,Jackson能准确创建MyResult实例,因此不会报错。
有两种常见的解决方式:
1. 让子类提供具体的TypeReference
在BaseAction中定义抽象方法,让子类返回对应的TypeReference:
public abstract class BaseAction<IT extends BaseInput, RT extends BaseResult> { // 抽象方法,子类实现返回具体的TypeReference protected abstract TypeReference<RT> getResultTypeReference(); public RT execute(final IT inObj) throws Exception { //... return JacksonFunctions.jacksonObjectMapper.readValue(jsonString, getResultTypeReference()); } }
子类MyAction实现该方法:
public class MyAction extends BaseAction<MyInput, MyResult> { @Override protected TypeReference<MyResult> getResultTypeReference() { return new TypeReference<MyResult>() {}; } }
2. 通过反射获取泛型实参类型
在BaseAction的构造函数中,通过反射获取子类传入的泛型实参RT的实际类型,构造对应的TypeReference:
public abstract class BaseAction<IT extends BaseInput, RT extends BaseResult> { private final TypeReference<RT> resultTypeRef; @SuppressWarnings("unchecked") public BaseAction() { // 获取当前类的泛型父类信息 Type genericSuperclass = getClass().getGenericSuperclass(); if (genericSuperclass instanceof ParameterizedType parameterizedType) { // 获取第二个泛型参数(RT对应的类型) Type type = parameterizedType.getActualTypeArguments()[1]; resultTypeRef = new TypeReference<RT>() { @Override public Type getType() { return type; } }; } else { throw new IllegalArgumentException("当前类未使用泛型参数"); } } public RT execute(final IT inObj) throws Exception { //... return JacksonFunctions.jacksonObjectMapper.readValue(jsonString, resultTypeRef); } }
这种方式无需子类额外实现方法,基类通过反射自动获取实际类型,但反射会带来一定性能开销,代码复杂度略高。
内容的提问来源于stack exchange,提问作者bobyuan

