深入理解 `ParameterizedTypeReference`:解决 Java 泛型擦除问题 ...

十念  论坛元老 | 2025-3-14 01:21:35 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1003|帖子 1003|积分 3009

在 Java 中,由于类型擦除的存在,我们在使用 RestTemplate 获取带有泛型的 HTTP 响应时,大概会遇到 泛型信息丢失 的问题。而 ParameterizedTypeReference<T> 正是用来解决这个问题的。
本文将深入解析 ParameterizedTypeReference 的作用、原理,并联合 RestTemplate 举例阐明如何正确解析泛型数据

1. 为什么 Java 需要 ParameterizedTypeReference?

在 Java 中,泛型的实现采用类型擦除(Type Erasure),即:


  • 编译时 泛型信息是完整的,例如 List<String> 和 List<Integer>。
  • 运行时 泛型信息被擦除,所有泛型类型都会变成 List,无法区分 List<String> 和 List<Integer>。
示例:Java 运行时擦除泛型

  1. List<String> list1 = new ArrayList<>();
  2. List<Integer> list2 = new ArrayList<>();
  3. System.out.println(list1.getClass() == list2.getClass()); // true
复制代码
输出:
  1. true
复制代码
可以看出,List<String> 和 List<Integer> 在运行时完全相同。

2. RestTemplate 解析泛型数据时的问题

假设我们有一个 API /api/users,返回 JSON 数据如下:
  1. {
  2.     "code": "SUCCESS",
  3.     "message": "请求成功",
  4.     "data": [
  5.         {
  6.             "id": 1,
  7.             "name": "Alice"
  8.         },
  9.         {
  10.             "id": 2,
  11.             "name": "Bob"
  12.         }
  13.     ]
  14. }
复制代码
对应的 Java 数据模型

  1. import lombok.Data;
  2. import java.util.List;
  3. @Data
  4. class User {
  5.     private Integer id;
  6.     private String name;
  7. }
  8. @Data
  9. class BaseResponse<T> {
  10.     private String code;
  11.     private String message;
  12.     private T data;
  13. }
复制代码
尝试使用 RestTemplate 获取泛型数据

  1. ResponseEntity<BaseResponse<List<User>>> response = restTemplate.exchange(
  2.     url,
  3.     HttpMethod.GET,
  4.     entity,
  5.     BaseResponse<List<User>>.class  // ❌ 这里会导致类型擦除
  6. );
复制代码
  问题: BaseResponse<List<User>>.class 在运行时 会被擦除成 BaseResponse<Object>,导致 JSON 反序列化失败!
  解决方案? ✅ 使用 ParameterizedTypeReference!

3. 使用 ParameterizedTypeReference 解析泛型数据

ParameterizedTypeReference<T> 的作用是 保留泛型信息,从而让 RestTemplate 正确解析带泛型的 JSON 数据。
✅ 正确写法

  1. import org.springframework.core.ParameterizedTypeReference;
  2. import org.springframework.http.*;
  3. import org.springframework.web.client.RestTemplate;
  4. import java.util.List;
  5. public class RestTemplateExample {
  6.     private static final RestTemplate restTemplate = new RestTemplate();
  7.     public static void main(String[] args) {
  8.         String url = "https://example.com/api/users";
  9.         // 创建请求头
  10.         HttpHeaders headers = new HttpHeaders();
  11.         headers.setContentType(MediaType.APPLICATION_JSON);
  12.         // 创建请求实体
  13.         HttpEntity<String> entity = new HttpEntity<>(null, headers);
  14.         // 使用 ParameterizedTypeReference 获取泛型数据
  15.         ResponseEntity<BaseResponse<List<User>>> response = restTemplate.exchange(
  16.                 url,
  17.                 HttpMethod.GET,
  18.                 entity,
  19.                 new ParameterizedTypeReference<BaseResponse<List<User>>>() {} // ✅ 保留泛型信息
  20.         );
  21.         // 解析返回数据
  22.         if (response.getStatusCode() == HttpStatus.OK) {
  23.             BaseResponse<List<User>> body = response.getBody();
  24.             if (body != null && "SUCCESS".equals(body.getCode())) {
  25.                 List<User> users = body.getData();
  26.                 users.forEach(user -> System.out.println(user.getName()));
  27.             }
  28.         }
  29.     }
  30. }
复制代码
ParameterizedTypeReference<T> 解决了什么?



  • 保留了 BaseResponse<List<User>> 的泛型信息,制止 Java 类型擦除
  • 让 RestTemplate 解析 JSON 时,知道 data 字段现实是 List<User>,从而正确反序列化。

4. ParameterizedTypeReference 的底层原理

我们来看 ParameterizedTypeReference 的 核心源码
  1. public abstract class ParameterizedTypeReference<T> {
  2.     private final Type type;
  3.     protected ParameterizedTypeReference() {
  4.         this.type = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
  5.     }
  6.     public Type getType() {
  7.         return this.type;
  8.     }
  9. }
复制代码
原理解析


  • ParameterizedTypeReference<T> 是 抽象类,不能直接实例化。
  • new ParameterizedTypeReference<BaseResponse<List<User>>>() {} 现实上是 匿名内部类,继续 ParameterizedTypeReference<T>。
  • 构造方法 中:

    • 通过 getClass().getGenericSuperclass() 获取 ParameterizedType。
    • getActualTypeArguments()[0] 解析出 BaseResponse<List<User>>,并存储到 type 变量中。

  • 这样,type 变量在运行时 不会被擦除,Spring 通过 type 解析泛型 JSON 数据。

5. 实用场景

实用于解析带泛型的 JSON 响应


  • List<T>、Map<String, T>、Set<T> 等 复杂数据结构
  • 实用于 Spring WebClient 以及 消息队列(Kafka)等泛型解析
示例:解析 Map<String, List<User>>

  1. ResponseEntity<BaseResponse<Map<String, List<User>>>> response = restTemplate.exchange(
  2.     url,
  3.     HttpMethod.GET,
  4.     entity,
  5.     new ParameterizedTypeReference<BaseResponse<Map<String, List<User>>>>() {}
  6. );
复制代码
  ParameterizedTypeReference 还能解析更复杂的泛型结构,如 Map<String, List<User>>,Spring 能够正确辨认 data 的数据结构!
  
6. 总结



  • Java 运行时会擦除泛型,导致 RestTemplate 无法正确解析泛型 JSON。
  • ParameterizedTypeReference<T> 通过匿名内部类的方式 保留泛型信息,解决类型擦除问题。
  • 实用于 RestTemplate、WebClient、Kafka 消耗者、Redis 泛型缓存 等场景
   发起:在 RestTemplate 解析泛型数据时,务必使用 ParameterizedTypeReference,否则 JSON 解析大概失败!
  
盼望这篇博客能帮助你理解 ParameterizedTypeReference 的作用和应用场景!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

十念

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表