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
}
相关推荐
Pniubi2 分钟前
HashMap扩容全流程
java·开发语言·数据结构
考虑考虑8 小时前
JDK26中的Map.ofLazy
java·后端·java ee
鶴哥只手遮天12 小时前
Spring AI 2.0.1 深度解析-第 14 章 MCP 服务端(上):注解式实现与工具来源
java·人工智能·开源
鶴哥只手遮天12 小时前
Spring AI 2.0.1 深度解析-第 15 章 MCP 服务端(下):三种协议形态、Streamable 传输与状态管理
java·人工智能·开源
鶴哥只手遮天12 小时前
Spring AI 2.0.1 深度解析-第 16 章 向量存储(上):`VectorStore` 接口体系、`BatchingStrategy`
java·人工智能·开源
鶴哥只手遮天12 小时前
Spring AI 2.0.1 深度解析-第 12 章 工具循环的流式形态、Usage 累加与扩展体系
java·人工智能·开源
鶴哥只手遮天12 小时前
Spring AI 2.0.1 深度解析-第 23 章 模型供应商实现:从 `OpenAiSetup` 到 Bedrock
java·人工智能·开源
鶴哥只手遮天12 小时前
Spring AI 2.0.1 深度解析-第 06 章 内置 Advisor 全景:13 个实现逐个剖到源码级
java·人工智能·开源
鶴哥只手遮天12 小时前
Spring AI 2.0.1 深度解析-第 03 章 消息体系与 Prompt:不可变模型、`ChatOptions` 与模板渲染
java·人工智能·开源
鶴哥只手遮天12 小时前
Spring AI 2.0.1 深度解析-第 24 章 生产实践与 2.x 迁移:陷阱清单、AOT 与调优
java·人工智能·开源