在 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
输出:
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
的作用和应用场景!🎯