文章连接:https://liuyueyi.github.io/hexblog/2018/05/30/180530-经过反射获取泛型类的实际参数/css
反射获取泛型类的实际参数
泛型用得仍是比较多的,那么如何获取泛型类上实际的参数类型呢?java
好比一个接口为git
public interface IBolt<T, K> { }
如今给一个IBolt的具体实现类,能够获取到实际的参数类型么?下面几种case能够怎么获取实际的IBolt中的T和K类型呢?github
// 实现接口方式 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
oop
a. 简单对比
- Type[] getGenericInterfaces
以Type的形式返回本类直接实现的接口.这样就包含了泛型参数信息学习
- Class[] getInterfaces
返回本类直接实现的接口.不包含泛型参数信息编码
b. 编码实现
一个基础的实现方式以下url
@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()); } } } }
简单分析上面实现:spa
- 首先是获取全部的接口信息,遍历接口,
- 若是这个接口是支持泛型的,则返回的type应该是
ParameterizedType
类型 - 获取原始类信息(主要目的是为了和目标类进行对比
IBolt.class.equals(ptype.getRawType())
) - 获取泛型类型
ptype.getActualTypeArguments()
输出结果以下:
java.lang.String java.lang.Boolean
上面这个实现针对ABolt还能够,可是换成 AEBolt 以后,即非直接实现目标接口的状况下,发现什么都获取不到,由于 IBolt.class.equals(ptype.getRawType())
这个条件不会知足,稍稍改一下,改为只要是IBolt的子类便可
@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) 不符,后面再说
java.lang.Boolean
2. 抽象类继承方式获取
抽象类与接口的主要区别在于类是单继承的,因此改为用 java.lang.Class#getGenericSuperclass
获取
a. 简单对比
- Type getGenericSuperclass()
返回父类的基本类信息,包含泛型参数信息
- Class<? super T> getSuperclass();
返回父类信息,不包含泛型
b. 代码实现
同上面的差很少,针对BBolt的实现,能够这么来
@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())
- 若是是,则直接获取参数信息
输出结果以下:
java.lang.String java.lang.Boolean
固然上面依然是存在和上面同样的问题,对于BEBolt这个类,输出的就和咱们预期的不一样,其输出只会有 EAbsBolt<Boolean>
上的信息,即到获取EAbsBolt这一层时,就结束了
java.lang.Boolean
若是咱们将上面的断定当前类是否为Ibolt.class,会输出什么呢?
- 什么都没有,由于Ibolt是接口,而获取父类是获取不到接口信息的,因此断定永远走不进去
II. 进阶实现
上面的基础实现中,都存在一些问题,特别是但继承结构比较复杂,深度较大时,其中又穿插着泛型类,致使不太好获取精确的类型信息,下面进行尝试探索,不保证能够成功
1. 接口实现方式
主要的目标就是能正常的分析AEBolt这个case,尝试思路以下:
- 层层往上,直到目标接口,而后获取参数类型
改进后的实现以下
@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 而言,输出为
java.lang.String T
若是改为 ARBolt, 即RBolt这个接口在继承IBolt接口的同时,指定了参数类型,这时输出如
java.lang.String java.lang.Boolean
也就是说这个思路是能够的,惟一的问题就是当实现目标接口的某一层接口,也是泛型时,直接定位到最底层,获取的就是T,K这种符号参数了,由于实际的类型参数信息,在上一层定义的
那么有没有办法将这个参数类型传递下去呢?
实际尝试了一下,再往下走就比较复杂了,感受有点得不偿失,不知道是否有相关的工具类
2. 继承类方式
接口方式实现以后,继承类方式也差很少了,并且相对而言会更简单一点,由于继承是单继承的
@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(); } } }
输出以下,一样有上面的问题
java.lang.String T
III. 小结
经过反射方式,后去泛型类的参数信息,有几个有意思的知识点:
-
获取泛型类信息
java.lang.Class#getGenericSuperclass java.lang.Class#getGenericInterfaces // 获取实际的泛型参数 java.lang.reflect.ParameterizedType#getActualTypeArguments
-
Class判断继承关系
java.lang.Class#isAssignableFrom // 父类做为调用方,子类做为参数
II. 其余
一灰灰Blog: https://liuyueyi.github.io/hexblog
一灰灰的我的博客,记录全部学习和工做中的博文,欢迎你们前去逛逛
声明
尽信书则不如,已上内容,纯属一家之言,因我的能力有限,不免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
- 微博地址: 小灰灰Blog
- QQ: 一灰灰/3302797840
扫描关注
