在编写单测时,经常需要使用 RestTemplate
来发送 HTTP 请求并接收响应,然后对返回的数据进行判断和处理。某次在写测试用例返回了一个 List 对象,在遍历 List 对象时报错” java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class xxx。实际上,当涉及到处理泛型集合对象(如 List<Dashboard>
或 Page<Dashboard>
)时,反序列化过程可能会遇到一些问题。
Java 在编译时会执行类型擦除(Type Erasure),这意味着所有泛型类型信息在编译时都会被移除。例如,List<Dashboard>
在运行时实际上只是一个 List
。这种类型擦除机制是为了兼容 Java 的早期版本,但它也带来了一个问题:在运行时,我们无法直接获取泛型类型的信息。
List<Dashboard> dashboards = new ArrayList<>(); Class<?> clazz = dashboards.getClass(); System.out.println(clazz); // 输出:class java.util.ArrayList
在上面的示例中,运行时只能看到 ArrayList
,而无法知道它实际包含的泛型类型 Dashboard
。
Spring Boot 使用 Jackson 作为默认的 JSON 处理库。Jackson 在反序列化 JSON 时需要知道目标类型的确切信息。对于非泛型类型(如 Dashboard
),Jackson 可以直接从类信息中获取反序列化所需的类型信息:
ObjectMapper mapper = new ObjectMapper();Dashboard dashboard = mapper.readValue(json, Dashboard.class);
但是,对于泛型类型(如 List<Dashboard>
),由于类型擦除的原因,Jackson 无法直接获取泛型类型信息,因此需要额外的类型信息:
ObjectMapper mapper = new ObjectMapper(); List<Dashboard> dashboards = mapper.readValue(json, new TypeReference<List<Dashboard>>() {});
TypeReference
是 Jackson 提供的一种解决方案,用于在运行时提供泛型类型信息。
在使用 RestTemplate
时,我们遇到的反序列化问题本质上与 Jackson 一样。当我们调用 restTemplate.getForObject(url, List.class)
时,RestTemplate
无法知道 List
的具体类型,因此无法正确反序列化为 List<Dashboard>
。
为了正确处理泛型集合对象,我们需要使用 ParameterizedTypeReference
提供泛型类型信息:
ResponseEntity<List<Dashboard>> response = restTemplate.exchange( url, HttpMethod.GET, null, new ParameterizedTypeReference<List<Dashboard>>() {} ); List<Dashboard> dashboards = response.getBody();
ParameterizedTypeReference
是 Spring 提供的一个解决方案,用于在运行时提供泛型类型信息,类似于 Jackson 的 TypeReference
。
下面是一个完整的示例,展示如何使用 RestTemplate
处理泛型集合对象 List<Dashboard>
和分页对象 Page<Dashboard>
的反序列化。
4.1 处理 List<Dashboard>
import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.util.List; @Service public class DashboardService { @Autowired private RestTemplate restTemplate; public List<Dashboard> getAllDashboards() { String url = "http://example.com/api/dashboards"; ResponseEntity<List<Dashboard>> response = restTemplate.exchange( url, HttpMethod.GET, null, new ParameterizedTypeReference<List<Dashboard>>() {} ); return response.getBody(); } }
4.2 处理 Page<Dashboard>
import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @Service public class DashboardService { @Autowired private RestTemplate restTemplate; public PageResponse<Dashboard> getDashboardsPage(int page, int size) { String url = String.format("http://example.com/api/dashboards?page=%d&size=%d", page, size); ResponseEntity<PageResponse<Dashboard>> response = restTemplate.exchange( url, HttpMethod.GET, null, new ParameterizedTypeReference<PageResponse<Dashboard>>() {} ); return response.getBody(); } }
类型安全性问题
由于泛型类型信息在编译期存在但运行期被擦除,那么一些类型检查就无法在运行期完成,可能导致类型不安全。
反射获取类型问题
通过反射无法获取参数化类型,只能获取原始类型,如List而无法获取List<String>。
序列化问题
无法通过原始类信息重新构造出参数化类型实例,影响对象的序列化和反序列化。
泛型数组创建问题
由于类型擦除,无法创建参数化类型的数组,如不能定义List<String>[]数组。
泛型转型问题
由于类型安全检查依赖于类型信息,类型擦除后就无法进行参数化类型之间的正确转型操作。
泛型继承问题
参数化类型的子类不能识别其父类原始类型的参数,带来一些继承限制。
泛型边界限制问题
泛型类型上带有范围限定<? extends T>或<? super T>也会因为类型擦除而丧失部分约束能力。
泛型特化问题
无法在运行期将generic类型具体化为其子类型。
所以总体来说,泛型擦除令Java泛型在一定程度上丧失了类型安全的完全性。
Java 的类型擦除机制导致在运行时无法直接获取泛型类型信息,这会影响到 RestTemplate
和 Jackson 在反序列化泛型集合对象时的行为。通过使用 ParameterizedTypeReference
或 Jackson 的 TypeReference
,我们可以在运行时提供泛型类型信息,从而正确反序列化泛型集合对象。