背景
在使用 RestTemplate
发送 HTTP 请求时,需要指定响应(如 User
)类型将结果进行反序列化。
1 restTemplate.exchange(url, HttpMethod.GET, null , User.class);
然而,现实中很多场景需要返回的是集合类型,像原来这样写直接报错,因为 Java 泛型是通过类型擦除来实现的,擦除后 List<User>
实际上是 List
,而 User
类型信息丢失了。
1 2 3 restTemplate.exchange(url, HttpMethod.GET, null , List<User>.class);
这时会用到 ParameterizedTypeReference
来指定返回值的类型。
1 2 3 4 5 6 restTemplate.exchange( "http://localhost:8080/hello" , HttpMethod.GET, null , new ParameterizedTypeReference <List<User>>() {} )
使用 ParameterizedTypeReference
ParameterizedTypeReference
来自 org.springframework.core
包下,可以“保留”泛型类型信息,使其可在 RestTemplate
中指定集合类型作为响应结果而规避类型擦除的麻烦。
下面是一个例子,展示了如何使用 ParameterizedTypeReference
来获取泛型中的类型信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.example.demo;import java.util.List;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.core.ParameterizedTypeReference;@SpringBootTest class DemoApplicationTests { @Test void testParameterizedTypeReference () { ParameterizedTypeReference<List<String>> typeRef = new ParameterizedTypeReference <List<String>>() {}; System.out.println(typeRef.getType().getTypeName()); } }
原理
ParameterizedTypeReference
本身是一个抽象类,是不可以被实例化的。
1 2 3 public abstract class ParameterizedTypeReference <T> { ... }
new ParameterizedTypeReference<List<String>>() {};
实际上是创建了一个匿名内部类,这个类是 ParameterizedTypeReference<List<String>>
的子类。为了进一步证明这一点,我们在 taget
目录下可以找到这个匿名内部类,反编译后代码如下。
1 2 3 4 5 6 7 8 9 10 11 package com.example.demo;import java.util.List;import org.springframework.core.ParameterizedTypeReference;class DemoApplicationTests$1 extends ParameterizedTypeReference <List<String>> { DemoApplicationTests$1 (final DemoApplicationTests this $0 ) { this .this $0 = this $0 ; } }
因此,typeRef
(new
出来的实例)就是 ParameterizedTypeReference<List<String>>
的子类的实例。明确指定了泛型类型并将其继承后,得到的子类是可以获取到父类的泛型类型的。下面举一个简单的例子(参考自 擦拭法 - Java教程 - 廖雪峰的官方网站 )。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import java.lang.reflect.ParameterizedType;import java.math.BigDecimal;import java.util.List;class Triple <A, B, C> {} class SpecialTriple extends Triple <Integer, List<BigDecimal>, Triple<Long, Double, Float>> {} public class Main { public static void main (String[] args) { if (SpecialTriple.class.getGenericSuperclass() instanceof ParameterizedType parameterizedType) { List.of(parameterizedType.getActualTypeArguments()).stream().forEach(System.out::println); } } }
源码解析
ParameterizedTypeReference
内部也是通过类似的方法获取到的泛型类型。
ParameterizedTypeReference
的构造函数是受保护的,这意味着它可以被我们构造的匿名内部类调用。在构造函数中,通过反射获取当前类的泛型超类,并从中提取泛型参数的类型信息。
1 2 3 4 5 6 7 8 9 10 11 12 protected ParameterizedTypeReference () { Class<?> parameterizedTypeReferenceSubclass = findParameterizedTypeReferenceSubclass(getClass()); Type type = parameterizedTypeReferenceSubclass.getGenericSuperclass(); Assert.isInstanceOf(ParameterizedType.class, type, "Type must be a parameterized type" ); ParameterizedType parameterizedType = (ParameterizedType) type; Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); Assert.isTrue(actualTypeArguments.length == 1 , "Number of type arguments must be 1" ); this .type = actualTypeArguments[0 ]; }