class java.util.LinkedHashMap cannot be cast to xxx

错误复现

这个报错是在学习微服务的过程中发现的,涉及到 Feign 远程调用。

一共有两个服务:user 和 user2。

user 的 Feign 接口如下:

java 复制代码
@FeignClient("user")
public interface UserClient {

    @GetMapping("/user/getUser")
    Result getUser(@RequestParam("id") int id);
}

接口实现如下:

java 复制代码
    @GetMapping("/user/getUser")
    @Override
    public Result getUser(int id) {
        return Result.success(new User("李华", 18));
    }

Result 是通用的返回类:

java 复制代码
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Result {
    private int status;
    private String msg;
    private Object data;

    public static Result success(Object data) {
        return new Result(1, null, data);
    }

    public static Result success() {
        return new Result(1, null, null);
    }
}

然后,在 user2 服务中调用了 user 的 Feign 接口:

java 复制代码
    @GetMapping("/user2/getUser")
    public Result getUser(int id) {
        Result result = userClient.getUser(id);
        Object data = result.getData();
        User user = (User) data;
        return Result.success(user);
    }

结果就报错了:

在 User user = (User) data 这里的强制类型转换错误了。

这里我就不懂了,Result.success(new User("李华", 18)),Object data = new User("李华", 18),从接口调用方得到的 data 也是 Oject 类型啊,Object data = result.getData()。

不应该是 Object data = new User("李华", 18),然后 User user = (User) data,这不是标准的向上转型和向下转型吗?为什么这个时候把 Object 转成 User 就报错了呢?

原因分析

问过 AI 才知道,因为这个对象跨服务了。

这个 User 对象从一个服务传输到了另一个服务,那这个 User 对象是怎么传输过去的呢?

加了 @RestController 注解,User 对象就是被转成 JSON 字符串传输过去的,然后被接口调用方从 JSON 解析得到 Result 对象。

关键就在这里,User 对象被转成了 JSON 字符串,JSON 会记录这个 User 对象的类型吗?并不会吧,所以 JSON 被解析后再转成 User 对象就报错了。

不对吧,如果 JSON 不知道这个 User 对象的类型,是怎么获取 User 的属性呢?{"status": 1, "msg": null, "data": {"name": "李华", "age": 18}},data 是 Object 类型啊,如果不知道 data 是 User 类型怎么知道有 name 和 age 呢?

在 java 对象 -> JSON 字符串的时候,因为 data = new User("李华", 18),框架中的 JSON 解析器可以通过反射获取 data 指向的 User 对象的属性,所以这个接口返回的 JSON 字符串是包含 User 对象的 name 和 age 的。

当这个 JSON 对象来到接口调用方的时候,因为在 UserClient 中已经确定了 getUser 这个接口返回的就是 Result 对象,所以 JSON 解析器会将 {"status": 1, "msg": null, "data": {"name": "李华", "age": 18}} 这个字符串转成 Result 对象。

这个 JSON 没有把 User 写在额头上吧,所以 JSON 解析器会把 {"name": "李华", "age": 18} 转成 Object,而不是转成 User 对象。

但是呢,JSON 解析器也不清楚 {"name": "李华", "age": 18} 到底是什么对象,但是 JSON 都是键值对,所以 JSON 解析器把 {"name": "李华", "age": 18} 转成了 LinkedHashMap<String, Object> 的对象,也就是 Object data = new LinkedHashMap<String, Object>()。

User user = (User) data 就是把 LinkedHashMap 对象转成 User 对象,所以报错了,提示:class java.util.LinkedHashMap cannot be cast to class org.example.model.User。

解决方案

既然现在知道 data 是一个 LinkedHashMap 对象,就可以解决问题了。

方案一:用 LinkedHashMap 的 API 获取 User 属性。

java 复制代码
    @GetMapping("/user2/getUser")
    public Result getUser(int id) {
        Result result = userClient.getUser(id);
        Object data = result.getData();
        LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>) data;
        User user = new User();
        String name = (String) map.get("name");
        Integer age = (Integer) map.get("age");
        user.setName(name);
        user.setAge(age);
        return Result.success(user);
    }

方案二:用 JSON 解析器转成 User 对象

java 复制代码
    @GetMapping("/user2/getUser")
    public Result getUser(int id) {
        Result result = userClient.getUser(id);
        Object data = result.getData();
        ObjectMapper objectMapper = new ObjectMapper();
        User user = objectMapper.convertValue(data, User.class);
        return Result.success(user);
    }

泛型尝试

原来这个报错是因为 JSON 转对象的时候不知道类型啊,那我把 Result 的 data 改成泛型不就行了吗?

好吧,进行一下泛型的尝试:

java 复制代码
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Result<T> {
    private int status;
    private String msg;
    private T data;

    public static <T> Result<T> success(T data) {
        return new Result<>(1, null, data);
    }

    public static Result success() {
        return new Result<>(1, null, null);
    }
}

如果只改 Result 的话,还是会报错的,因为你没改接口。

接口返回的还是 Result,没有指定返回值的类型。java 是有泛型擦除的,所以接口调用方还是会把 JSON 字符串转成 Result 对象,把 data 字符串转成 LinkedHashMap,令 data = LinkedHashMap 对象,强转还是报错。

需要手动指定返回值的类型才可以自动转换:

java 复制代码
    @GetMapping("/user/getUser")
    Result<User> getUser(@RequestParam("id") int id);
java 复制代码
    @GetMapping("/user2/getUser")
    public Result<User> getUser(int id) {
        Result<User> result = userClient.getUser(id);
        User user = result.getData();
        return Result.success(user);
    }

现在直接获取到的就是 User 对象了,JSON 解析器在底层已经帮你完成转换流程了。

泛型不是会被擦除吗?怎么这里又知道是 User 类型了?

泛型确实会被擦除啊,但是接口方法都写明了返回的是一个 User 类对象,Result<User> result = userClient.getUser(id),JSON 解析器就知道了要把 data 转成 User 对象。

那我如果不需要在 Result 对象中设置 data 呢?

那就让 data 等于 null 就可以了。

java 复制代码
    public static <T> Result<T> success() {
        return new Result<>(1, null, null);
    }

Controller 层的接口返回 Result<Void> 表示这个接口没有 data,JSON 字符串中 data 为 null。

java 复制代码
    @GetMapping("/void-test")
    public Result<Void> voidTest() {
        return Result.success();
    }
java 复制代码
{
  "status": 1,
  "msg": null,
  "data": null
}
相关推荐
陈皮波比茶1 小时前
计算机二级MySQL笔记
java·数据库
木井巳1 小时前
【BFS/DFS 解决 FloodFill 算法】衣橱整理
java·算法·leetcode·深度优先·广度优先·宽度优先
zzzll11111 小时前
LangChain 1.3 新特性详解与实战指南
java·数据库·langchain
Knight_AL1 小时前
EasyExcel 导入模板踩坑:row.add(““)导致文本格式失效与单元格保护问题解决方案
java·前端·easyexcel
霸道流氓气质1 小时前
Spring AI Alibaba 系列总结:Java 工程师 AI 能力全景图谱
java·人工智能·spring
二十雨辰2 小时前
[Java]-场景题
java·开发语言
MZA6662 小时前
Spring Boot 实战:基于自定义注解 + AOP 实现 Kafka 消息无侵入异步上送
java·spring boot·kafka
我是大猴子2 小时前
为什么SSE无法代替Webscoket
java
重庆小透明2 小时前
Kafka 完全指南:从基础组件到核心原理(包含面试题)
java·分布式·微服务·架构·kafka