logo头像

寒墨轩

—— 墨寒的👨‍💻码疯窝

Java反射之获取泛型类定义的具体类型

通过上一篇Java反射之表示所有类型的Type接口一文,我们知道 Type 接口及其五大组件是用来描述Java的各种类型,主要包括原始类型和泛型类型。很多时候,我们需要获取到泛型类上定义的具体类型,从而完成一些业务逻辑。比如,最常见的情景就是JSON的反序列化,需要将JSON字符串反序列化为泛型的具体类型,比如反序列化为 List<User>,这就要求每一个 List 中的元素都是 User 对象。那么,如何获取到泛型类上定义的具体类型呢?这就是本文要阐述的内容。

1. 通过Class能获取泛型类型吗?

/2021/11/09/reflect-class.html中说过,Class 类上有一个 getTypeParameters() 方法用来获取类上的类型变量。通过它能不能获取到泛型类型呢?看下边的示例:

1
2
class Parent<T> { (1)
}
1 定义一个泛型类

这里定义一个泛型类,现在尝试通过它的实例对象来获取泛型类型,代码如下:

1
2
3
4
5
public void printParent() {
Parent parent = new Parent<>(); (1)
TypeVariable>[] parentTypeParameters = parent.getClass().getTypeParameters(); (2)
System.out.println(Arrays.toString(parentTypeParameters));
}
1 创建 Parent 类的实例
2 获取类型变量

上边的示例并不能拿到实例上的 String 类型,而只能拿到 Parent 类上定义的泛型类型 T,因此,输出结果为:

[T]

很显然,这个结果不是我们想要的,我们想要获取到 String 这个类。在 Java反射之创建泛型数组 一文中说过,泛型是编译期特性,在运行时泛型会被擦除。因此,我们想通过`Class` 在运行时用反射直接去获取泛型类型,看起来是不行的。

既然直接获取不行,那么有没有别的办法呢?

在上一篇Java反射之表示所有类型的Type接口一文中说过,Type 的参数化类型组件 ParameterizedType 可以获取整个泛型定义,那么要获取类上的泛型,肯定少不了它

但是,Field 通过 getGenericType() 方法可以获取到参数化类型,ConstructorMethod 可以通过 getGenericParameterTypes() 获取泛型参数或者通过 getGenericReturnType() 方法获取泛型值的泛型类型,而翻看 Class 类的 api,与泛型相关的看似有用的只有两个方法:

1
2
public Type[] getGenericInterfaces(); (1)
public Type getGenericSuperclass()
; (2)
1 获取类上的实现的泛型接口
2 获取类的泛型父类

它们是不是我们所需要的呢?看下边的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class GenericActualType {
interface GenericInterface1<T> { (1)
}

interface GenericInterface2<T> { (1)
}

class GenericSuperClass<T> { (2)
}

class GenericCls extends GenericSuperClass<Integer> implements GenericInterface1<String>, GenericInterface2<Long> { (3)
}

public void genericSuperClassAndInterface() { (4)
Type genericSuperclass = GenericCls.class.getGenericSuperclass();
Type[] genericInterfaces = GenericCls.class.getGenericInterfaces();
System.out.println(genericSuperclass.getClass());
for (Type genericInterface : genericInterfaces) {
System.out.println(genericInterface.getClass());
}
}
}
1 创建泛型接口
2 创建泛型父类
3 创建泛型类分别继承和实现了泛型父类和接口
4 测试方法

示例中,标记1、2分别申明了两个泛型接口和一个泛型类,然后声明一个 GenericCls 类来继承和实现它们。最后,通过测试方法,打印出 getGenericSuperclass()getGenericInterfaces() 获取到的泛型类型,其结果输出如下:

class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl

可以看到,通过这两个方法,确实能够拿到参数化类型 ParameterizedType,然后通过它来获取泛型具体类型就很容易了,前一篇已经介绍过,不再赘述。

2. 为什么只能通过父类或接口来获取到子类的泛型?

其实这个问题是Java类体系决定的。

首先,泛型类在实例化时才知道具体类型,但是泛型类型被擦除,所以反射获取不到具体类型,只能获取到泛型类申明时定义的诸如 TE 之类的东西:

1
Parent parent = new Parent<>();

其次,子类继承或实现泛型父类或接口,就获得了具体的泛型定义,如下所示:

1
2
class GenericCls extends GenericSuperClass<Integer> {
}

子类 GenericCls 在编译期和运行期都知道其泛型类型是 Integer,这个泛型类型已经具体化了,所以能够获取到具体泛型类型。

所以,有的书上说Java中的泛型并不是真正意义的泛型。

3. 通用的获取类定义的具体泛型方案

有了前边的了解,现在我们可以得到一个思路:要获取泛型类的具体泛型类型,需要创建其子类并持有具体类型。看一个例子:

1
2
3
4
5
6
7
// List list = new ArrayList(); (1)
List list = new ArrayList() {
}; (2)
Type genericSuperclass = list.getClass().getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
System.out.println(Arrays.toString(actualTypeArguments));
1 不能获取的泛型类型
2 通过创建 ArrayList 的匿名子类来获取泛型类型

注意标记1和2的区别,2是创建了 ArrayList 的匿名子类,从而通过 getGenericSuperclass() 就能获取泛型真实类型。上边的示例输出:

[class java.lang.String]

这种方式虽然有用,但是不够优雅,可以编写一个专门负责泛型类型具体化的抽象父类,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
abstract class ParameterizedTypeRef<T> {
private Type type; (1)

public ParameterizedTypeRef()
{
Class cls = findParameterizedTypeClass(this.getClass()); (2)
Type genericSuperclass = cls.getGenericSuperclass();
assert genericSuperclass instanceof ParameterizedType;
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
this.type = parameterizedType.getActualTypeArguments()[0]; (3)
}

public Type getType() { (4)
return type;
}

private static Class findParameterizedTypeClass(Class child) {
Class parent = child.getSuperclass();
if (parent == Object.class) // 没找到,抛异常
throw new IllegalArgumentException()
;
if (parent == ParameterizedTypeRef.class) // 找到,直接返回
return child
;
return findParameterizedTypeClass(parent); // 递归查找
}
}
1 持有泛型具体类型的成员域
2 找到父类是ParameterizedTypeRef的类,从而根据父类可以获取其泛型类型
3 通过参数化类型获取具体泛型类型
4 返回具体泛型类型

然后,其使用方法如下:

1
2
3
4
5
6
7
8
9
ParameterizedTypeRef parameterizedTypeRef = new ParameterizedTypeRef() {
};
System.out.println(parameterizedTypeRef.getType()); // class java.lang.String
ParameterizedTypeRef intParameterizedTypeRef = new ParameterizedTypeRef() {
};
System.out.println(intParameterizedTypeRef.getType());
ParameterizedTypeRef> parentParameterizedTypeRef = new ParameterizedTypeRef>() {
};
System.out.println(parentParameterizedTypeRef.getType().getTypeName()); // com.belonk.lang.generic.Parent

上边的示例通过创建 ParameterizedTypeRef 的匿名子类来具体化泛型参数。

其实,很多框架都是采用这种方式来获取具体泛型的:

1、Spring中有一个 ParameterizedTypeReference 类,来做同样的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class ParameterizedTypeReference {

private final Type type;


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];
}

// 省略其他代码
}

2、Jackson中的解决方案

Jackson在反序列化是,可以通过传递 TypeReference 对象的匿名子类来决定底层json需要反序列化的对象,比如:

1
List users = objectMapper.readValue(json, new TypeReference>() {});

示例中,使用 ObjectMapper 直接将json反序列化 List<User> 类型的对象,list中每一个元素都是 User,其实就是通过 TypeReference 来持有具体泛型实现的,TypeReference 定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class TypeReference<T> implements Comparable<TypeReference<T>> {
protected final Type _type;

protected TypeReference() {
Type superClass = this.getClass().getGenericSuperclass();
if (superClass instanceof Class) {
throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
} else {
this._type = ((ParameterizedType)superClass).getActualTypeArguments()[0];
}
}

public Type getType() {
return this._type;
}

public int compareTo(TypeReference o) {
return 0;
}
}

4. 总结

Java不能通过 Class 直接获取到类的具体泛型,但是可以通过创建泛型父类持有具体泛型并创建其匿名子类来实现。

本文示例代码见: github.

支付宝打赏 微信打赏

赞赏是不耍流氓的鼓励