180530-通过反射获取泛型类的实际参数

文章目录
  1. 反射获取泛型类的实际参数
    1. I. 基本姿势
      1. 1. 接口实现方式获取
        1. a. 简单对比
        2. b. 编码实现
      2. 2. 抽象类继承方式获取
        1. a. 简单对比
        2. b. 代码实现
    2. II. 进阶实现
      1. 1. 接口实现方式
      2. 2. 继承类方式
    3. III. 小结
    4. II. 其他
      1. 一灰灰Blog: https://liuyueyi.github.io/hexblog
      2. 声明
      3. 扫描关注

反射获取泛型类的实际参数

泛型用得还是比较多的,那么如何获取泛型类上实际的参数类型呢?

比如一个接口为

1
2
public interface IBolt<T, K> {
}

现在给一个IBolt的具体实现类,可以获取到实际的参数类型么?下面几种case可以怎么获取实际的IBolt中的T和K类型呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 实现接口方式
public class ABolt implements IBolt<String, Boolean>{}
public class AFBolt<T> implements IBolt<String, T> {}
public interface EBolt<T> extends IBolt<String, T> {}
public class AEBolt implements EBolt<Boolean> {}
public interface RBolt extends IBolt<String, Boolean>{}
public class ARBolt implements RBolt{}


// 继承抽象类方式
public abstract class AbsBolt<T,K> implements IBolt<T,K> {}
public class BBolt extends AbsBolt<String, Boolean> {}
public abstract class EAbsBolt<T> implements IBolt<String, T> {}
public class BEBolt extends EAbsBolt<Boolean> {}

I. 基本姿势

首先拿最简单的两个case来进行分析,一个是 ABolt, 一个是BBolt,根据这两个类信息来获取对应的泛型类型;

1. 接口实现方式获取

主要借助的就是右边这个方法:java.lang.Class#getGenericInterfaces

a. 简单对比

  1. Type[] getGenericInterfaces

以Type的形式返回本类直接实现的接口.这样就包含了泛型参数信息

  1. Class[] getInterfaces

返回本类直接实现的接口.不包含泛型参数信息

b. 编码实现

一个基础的实现方式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void testGetTypes() {
Type[] types = ABolt.class.getGenericInterfaces();
ParameterizedType ptype;
for (Type type: types) {
if (!(type instanceof ParameterizedType)) { // 非泛型类型,直接丢掉
continue;
}

ptype = (ParameterizedType) type;
if (IBolt.class.equals(ptype.getRawType())) {
// 如果正好是我们需要获取的IBolt对象,则直接获取
Type[] parTypes = ptype.getActualTypeArguments();
for (Type par: parTypes) {
System.out.println(par.getTypeName());
}
}
}
}

简单分析上面实现:

  • 首先是获取所有的接口信息,遍历接口,
  • 如果这个接口是支持泛型的,则返回的type应该是ParameterizedType类型
  • 获取原始类信息(主要目的是为了和目标类进行对比 IBolt.class.equals(ptype.getRawType())
  • 获取泛型类型 ptype.getActualTypeArguments()

输出结果如下:

1
2
java.lang.String
java.lang.Boolean

上面这个实现针对ABolt还可以,但是换成 AEBolt 之后,即非直接实现目标接口的情况下,发现什么都获取不到,因为 IBolt.class.equals(ptype.getRawType()) 这个条件不会满足,稍稍改一下,改成只要是IBolt的子类即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void testGetTypes() {
Type[] types = AEBolt.class.getGenericInterfaces();
ParameterizedType ptype;
for (Type type: types) {
if (!(type instanceof ParameterizedType)) { // 非泛型类型,直接丢掉
continue;
}

ptype = (ParameterizedType) type;
if (ptype.getRawType() instanceof Class && IBolt.class.isAssignableFrom((Class<?>) ptype.getRawType())) {
// 如果正好是我们需要获取的IBolt对象,则直接获取
Type[] parTypes = ptype.getActualTypeArguments();
for (Type par: parTypes) {
System.out.println(par.getTypeName());
}
}
}
}

此时输出为如下,实际上只是EBolt上的泛型类型,与我们期望的输出 (String, Boolean) 不符,后面再说

1
java.lang.Boolean

2. 抽象类继承方式获取

抽象类与接口的主要区别在于类是单继承的,所以改成用 java.lang.Class#getGenericSuperclass 获取

a. 简单对比

  1. Type getGenericSuperclass()

返回父类的基本类信息,包含泛型参数信息

  1. Class<? super T> getSuperclass();

返回父类信息,不包含泛型

b. 代码实现

同上面的差不多,针对BBolt的实现,可以这么来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Test
public void testGetAbsTypes() {
Class basicClz = BBolt.class;
Type type;
ParameterizedType ptype;
while (true) {
if (Object.class.equals(basicClz)) {
break;
}

type = basicClz.getGenericSuperclass();
if (!(type instanceof ParameterizedType)) {
basicClz = basicClz.getSuperclass();
continue;
}

ptype = (ParameterizedType) type;
if (ptype.getRawType() instanceof Class && IBolt.class.isAssignableFrom((Class<?>) ptype.getRawType())) {
Type[] parTypes = ptype.getActualTypeArguments();
for (Type par : parTypes) {
System.out.println(par.getTypeName());
}
break;
} else {
basicClz = basicClz.getSuperclass();
}
}
}

针对上面代码简单进行分析,步骤如下:

  • 获取父类(包含泛型)信息
  • 如果父类没有泛型信息,则继续往上获取父类信息
  • 包含泛型信息之后,判断这个类是否为我们预期的目标类 IBolt.class.isAssignableFrom((Class<?>) ptype.getRawType())
  • 如果是,则直接获取参数信息

输出结果如下:

1
2
java.lang.String
java.lang.Boolean

当然上面依然是存在和上面一样的问题,对于BEBolt这个类,输出的就和我们预期的不同,其输出只会有 EAbsBolt<Boolean> 上的信息,即到获取EAbsBolt这一层时,就结束了

1
java.lang.Boolean

如果我们将上面的判定当前类是否为Ibolt.class,会输出什么呢?

  • 什么都没有,因为Ibolt是接口,而获取父类是获取不到接口信息的,所以判定永远走不进去

II. 进阶实现

上面的基础实现中,都存在一些问题,特别是但继承结构比较复杂,深度较大时,其中又穿插着泛型类,导致不太好获取精确的类型信息,下面进行尝试探索,不保证可以成功

1. 接口实现方式

主要的目标就是能正常的分析AEBolt这个case,尝试思路如下:

  • 层层往上,直到目标接口,然后获取参数类型

改进后的实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Test
public void testGetTypes() {
// Class basicClz = ARBolt.class;
Class basicClz = AEBolt.class;
Type[] types;
ParameterizedType ptype;
types = basicClz.getGenericInterfaces();
boolean loop = false;
while (true) {
if (types.length == 0) {
break;
}

for (Type type : types) {
if (type instanceof Class) {
if (IBolt.class.isAssignableFrom((Class<?>) type)) {
// 即表示有一个继承了IBolt的接口,完成了IBolt的泛型参数定义
// 如: public interface ARBolt extends IBolt<String, Boolean>
types = ((Class) type).getGenericInterfaces();
loop = true;
break;
} else { // 不是预期的类,直接pass掉
continue;
}
}

ptype = (ParameterizedType) type;
if (ptype.getRawType() instanceof Class) {
if (!IBolt.class.isAssignableFrom((Class<?>) ptype.getRawType())) {
continue;
}

if (IBolt.class.equals(ptype.getRawType())) {
// 如果正好是我们需要获取的IBolt对象,则直接获取
Type[] parTypes = ptype.getActualTypeArguments();
for (Type par : parTypes) {
System.out.println(par.getTypeName());
}
return;
} else { // 需要根据父类来获取参数信息,重新进入循环
types = ((Class) ptype.getRawType()).getGenericInterfaces();
loop = true;
break;
}
}
}

if (!loop) {
break;
}
}
}

上面的实现相比较之前的负责不少,首先来看针对 AEBolt 而言,输出为

1
2
java.lang.String
T

如果改成 ARBolt, 即RBolt这个接口在继承IBolt接口的同时,指定了参数类型,这时输出如

1
2
java.lang.String
java.lang.Boolean

也就是说这个思路是可以的,唯一的问题就是当实现目标接口的某一层接口,也是泛型时,直接定位到最底层,获取的就是T,K这种符号参数了,因为实际的类型参数信息,在上一层定义的

那么有没有办法将这个参数类型传递下去呢?

实际尝试了一下,再往下走就比较复杂了,感觉有点得不偿失,不知道是否有相关的工具类

2. 继承类方式

接口方式实现之后,继承类方式也差不多了,而且相对而言会更简单一点,因为继承是单继承的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Test
public void testGetAbsTypes() {
Class basicClz = BEBolt.class;
Type type;
ParameterizedType ptype;
while (true) {
if (Object.class.equals(basicClz)) {
break;
}

type = basicClz.getGenericSuperclass();
if (!(type instanceof ParameterizedType)) {
basicClz = basicClz.getSuperclass();
continue;
}

ptype = (ParameterizedType) type;

if (Object.class.equals(basicClz.getSuperclass().getSuperclass())) { // 如果ptype的父类为Object,则直接分析这个
Type[] parTypes = ptype.getActualTypeArguments();
for (Type par : parTypes) {
System.out.println(par.getTypeName());
}
break;
} else {
basicClz = basicClz.getSuperclass();
}

}
}

输出如下,同样有上面的问题

1
2
java.lang.String
T

III. 小结

通过反射方式,后去泛型类的参数信息,有几个有意思的知识点:

  1. 获取泛型类信息

    1
    2
    3
    4
    5
    java.lang.Class#getGenericSuperclass
    java.lang.Class#getGenericInterfaces

    // 获取实际的泛型参数
    java.lang.reflect.ParameterizedType#getActualTypeArguments
  2. Class判断继承关系

    1
    2
    java.lang.Class#isAssignableFrom
    // 父类作为调用方,子类作为参数

II. 其他

一灰灰Bloghttps://liuyueyi.github.io/hexblog

一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

声明

尽信书则不如,已上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

扫描关注

QrCode

# Java

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×