在 Java 中,由于类型擦除的存在,我们在使用 RestTemplate 获取带有泛型的 HTTP 响应时,大概会遇到 泛型信息丢失 的问题。而 ParameterizedTypeReference<T> 正是用来解决这个问题的。
本文将深入解析 ParameterizedTypeReference 的作用、原理,并联合 RestTemplate 举例阐明如何正确解析泛型数据。
1. 为什么 Java 需要 ParameterizedTypeReference?
在 Java 中,泛型的实现采用类型擦除(Type Erasure),即:
- 编译时 泛型信息是完整的,例如 List<String> 和 List<Integer>。
- 运行时 泛型信息被擦除,所有泛型类型都会变成 List,无法区分 List<String> 和 List<Integer>。
示例:Java 运行时擦除泛型
- List<String> list1 = new ArrayList<>();
- List<Integer> list2 = new ArrayList<>();
- System.out.println(list1.getClass() == list2.getClass()); // true
复制代码 输出:
可以看出,List<String> 和 List<Integer> 在运行时完全相同。
2. RestTemplate 解析泛型数据时的问题
假设我们有一个 API /api/users,返回 JSON 数据如下:
- {
- "code": "SUCCESS",
- "message": "请求成功",
- "data": [
- {
- "id": 1,
- "name": "Alice"
- },
- {
- "id": 2,
- "name": "Bob"
- }
- ]
- }
复制代码 对应的 Java 数据模型
- import lombok.Data;
- import java.util.List;
- @Data
- class User {
- private Integer id;
- private String name;
- }
- @Data
- class BaseResponse<T> {
- private String code;
- private String message;
- private T data;
- }
复制代码 尝试使用 RestTemplate 获取泛型数据
- ResponseEntity<BaseResponse<List<User>>> response = restTemplate.exchange(
- url,
- HttpMethod.GET,
- entity,
- BaseResponse<List<User>>.class // ❌ 这里会导致类型擦除
- );
复制代码 问题: BaseResponse<List<User>>.class 在运行时 会被擦除成 BaseResponse<Object>,导致 JSON 反序列化失败!
解决方案? ✅ 使用 ParameterizedTypeReference!
3. 使用 ParameterizedTypeReference 解析泛型数据
ParameterizedTypeReference<T> 的作用是 保留泛型信息,从而让 RestTemplate 正确解析带泛型的 JSON 数据。
✅ 正确写法
- import org.springframework.core.ParameterizedTypeReference;
- import org.springframework.http.*;
- import org.springframework.web.client.RestTemplate;
- import java.util.List;
- public class RestTemplateExample {
- private static final RestTemplate restTemplate = new RestTemplate();
- public static void main(String[] args) {
- String url = "https://example.com/api/users";
- // 创建请求头
- HttpHeaders headers = new HttpHeaders();
- headers.setContentType(MediaType.APPLICATION_JSON);
- // 创建请求实体
- HttpEntity<String> entity = new HttpEntity<>(null, headers);
- // 使用 ParameterizedTypeReference 获取泛型数据
- ResponseEntity<BaseResponse<List<User>>> response = restTemplate.exchange(
- url,
- HttpMethod.GET,
- entity,
- new ParameterizedTypeReference<BaseResponse<List<User>>>() {} // ✅ 保留泛型信息
- );
- // 解析返回数据
- if (response.getStatusCode() == HttpStatus.OK) {
- BaseResponse<List<User>> body = response.getBody();
- if (body != null && "SUCCESS".equals(body.getCode())) {
- List<User> users = body.getData();
- users.forEach(user -> System.out.println(user.getName()));
- }
- }
- }
- }
复制代码 ParameterizedTypeReference<T> 解决了什么?
- 保留了 BaseResponse<List<User>> 的泛型信息,制止 Java 类型擦除。
- 让 RestTemplate 解析 JSON 时,知道 data 字段现实是 List<User>,从而正确反序列化。
4. ParameterizedTypeReference 的底层原理
我们来看 ParameterizedTypeReference 的 核心源码:
- public abstract class ParameterizedTypeReference<T> {
- private final Type type;
- protected ParameterizedTypeReference() {
- this.type = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
- }
- public Type getType() {
- return this.type;
- }
- }
复制代码 原理解析
- 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>>
- ResponseEntity<BaseResponse<Map<String, List<User>>>> response = restTemplate.exchange(
- url,
- HttpMethod.GET,
- entity,
- new ParameterizedTypeReference<BaseResponse<Map<String, List<User>>>>() {}
- );
复制代码 ParameterizedTypeReference 还能解析更复杂的泛型结构,如 Map<String, List<User>>,Spring 能够正确辨认 data 的数据结构!
6. 总结
- Java 运行时会擦除泛型,导致 RestTemplate 无法正确解析泛型 JSON。
- ParameterizedTypeReference<T> 通过匿名内部类的方式 保留泛型信息,解决类型擦除问题。
- 实用于 RestTemplate、WebClient、Kafka 消耗者、Redis 泛型缓存 等场景。
发起:在 RestTemplate 解析泛型数据时,务必使用 ParameterizedTypeReference,否则 JSON 解析大概失败!
盼望这篇博客能帮助你理解 ParameterizedTypeReference 的作用和应用场景! |