背景

在使用 RestTemplate 发送 HTTP 请求时,需要指定响应(如 User)类型将结果进行反序列化。

1
restTemplate.exchange(url, HttpMethod.GET, null, User.class);

然而,现实中很多场景需要返回的是集合类型,像原来这样写直接报错,因为 Java 泛型是通过类型擦除来实现的,擦除后 List<User> 实际上是 List,而 User 类型信息丢失了。

1
2
3
// ERROR: 错误代码
restTemplate.exchange(url, HttpMethod.GET, null, List<User>.class);
// ERROR: 错误代码

这时会用到 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>>() {};
// 输出 `java.util.List<java.lang.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
// Source code is decompiled from a .class file using FernFlower decompiler.
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;
}
}

因此,typeRefnew 出来的实例)就是 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);
}
}
}

// 输出:
// class java.lang.Integer
// java.util.List<java.math.BigDecimal>
// Triple<java.lang.Long, java.lang.Double, java.lang.Float>

源码解析

ParameterizedTypeReference 内部也是通过类似的方法获取到的泛型类型。

ParameterizedTypeReference的构造函数是受保护的,这意味着它可以被我们构造的匿名内部类调用。在构造函数中,通过反射获取当前类的泛型超类,并从中提取泛型参数的类型信息。

1
2
3
4
5
6
7
8
9
10
11
12
protected ParameterizedTypeReference() {
// 递归查找当前类的父类,直到找到ParameterizedTypeReference的直接子类。
Class<?> parameterizedTypeReferenceSubclass = findParameterizedTypeReferenceSubclass(getClass());
// 获取parameterizedTypeReferenceSubclass的泛型超类。这个超类实际上就是ParameterizedTypeReference<T>,其中T是泛型参数。
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];
}