LinkedHashMap cannot be cast to XXXDto:泛型擦除引发的类型转换异常

118 阅读3分钟

问题现象

调用某个查询接口时报错:

{
    "status": 500,
    "error": "Internal Server Error",
    "message": "java.util.LinkedHashMap cannot be cast to com.xxx.entity.QueryDto",
    "path": "/api/query/list.do"
}

请求参数:

{
    "name": "",
    "code": "",
    "type": "5",
    "pageIndex": 1,
    "pageSize": 20,
    "param": {
        "name": "",
        "code": "",
        "type": "5"
    }
}

问题分析

1. 代码调用链路

Controller 层

@RequestMapping(value = "list.do", method = RequestMethod.POST)
public ListResult<ResponseVO> queryList(@RequestBody QueryRequestDTO request) {
    return businessService.queryList(request);
}

Service 层

public ListResult<ResponseVO> queryList(ParamPage<QueryDto> request) {
    QueryDto param = request.getParam();  // 💥 这里抛出异常
    // ...
}

2. 类型继承关系

问题代码

// ❌ 继承泛型类但未指定类型参数
public class QueryRequestDTO extends ParamPage implements Serializable {
    private String name;
    private String code;
    private String type;
    // ...
}

基类定义

public class ParamPage<T> extends ParamBase<T> {
    int pageIndex = -1;
    int pageSize = -1;
}

public class ParamBase<T> implements Serializable {
    T param;  // 泛型字段
}

3. 问题根源

关键点QueryRequestDTO extends ParamPage没有指定泛型参数

执行流程

  1. JSON 反序列化阶段

    • Jackson 接收到 JSON,需要反序列化为 QueryRequestDTO
    • QueryRequestDTO 继承了 ParamPage<T>,但没有指定 T 的类型
    • 由于泛型擦除,运行时 T 的类型信息丢失
    • Jackson 无法确定 param 字段的具体类型
    • Jackson 默认将嵌套的 JSON 对象反序列化为 LinkedHashMap
  2. 方法调用阶段

    • Controller 调用 businessService.queryList(request)
    • QueryRequestDTOParamPage 的子类,符合多态,编译通过
    • 实际传入的对象中,param 字段是 LinkedHashMap
  3. 类型转换阶段

    • Service 方法签名:queryList(ParamPage<QueryDto> request)
    • 代码执行:QueryDto param = request.getParam()
    • 实际类型:getParam() 返回的是 LinkedHashMap
    • 期望类型:QueryDto
    • 抛出 ClassCastException

问题结论

这是一个泛型类型参数缺失导致的 JSON 反序列化异常。

  • 直接原因QueryRequestDTO 继承 ParamPage 时未指定泛型参数
  • 技术原因:Java 泛型擦除导致 Jackson 无法推断 param 字段类型,默认使用 LinkedHashMap
  • 触发条件:Service 方法尝试将 LinkedHashMap 强转为 QueryDto

解决方案

方案一:指定泛型参数(推荐)

// ✅ 正确:明确指定泛型类型
public class QueryRequestDTO extends ParamPage<QueryDto> implements Serializable {
    private String name;
    private String code;
    private String type;
    // ...
}

优点

  • 类型安全,编译期就能发现问题
  • Jackson 可以正确反序列化 param 字段
  • 无需修改其他代码

方案二:不使用 param 字段

既然 QueryRequestDTO 已经有了自己的业务字段,就不需要再使用继承来的 param 字段:

// 修改 Service 方法
public ListResult<ResponseVO> queryList(QueryRequestDTO dto) {
    // 直接使用 dto 的字段
    String name = dto.getName();
    String code = dto.getCode();
    // ...
}

优点

  • 避免泛型问题
  • 代码更清晰,不需要通过 getParam() 获取数据

核心教训

  1. 继承泛型类必须指定类型参数
    extends ParamPage
    extends ParamPage<QueryDto>

  2. 泛型擦除会导致 JSON 反序列化失败
    运行时泛型类型信息丢失,Jackson 无法推断字段类型,默认使用 LinkedHashMap

  3. 编译通过不代表运行正确
    由于多态,QueryRequestDTO 可以赋值给 ParamPage<QueryDto>,编译器不会报错,但运行时泛型参数不匹配会导致 ClassCastException

  4. 设计要合理
    如果 DTO 已经有了业务字段,就不应该再使用继承来的 param 字段,这种设计容易产生混淆

总结

一句话QueryRequestDTO 继承泛型类 ParamPage<T> 时未指定类型参数,导致 Jackson 将 param 字段反序列化为 LinkedHashMap,而代码期望的是 QueryDto,最终抛出类型转换异常。

解决方法:继承时明确指定泛型参数 extends ParamPage<QueryDto>