美文网首页码农日历
180530-反射获取泛型类的实际参数

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

作者: 一灰灰blog | 来源:发表于2018-05-31 22:54 被阅读37次

    文章链接:https://liuyueyi.github.io/hexblog/2018/05/30/180530-通过反射获取泛型类的实际参数/

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

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

    比如一个接口为

    public interface IBolt<T, K> {
    }
    

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

    // 实现接口方式
    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. 编码实现

    一个基础的实现方式如下

    @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()

    输出结果如下:

    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. 简单对比

    1. Type getGenericSuperclass()

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

    1. 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. 小结

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

    1. 获取泛型类信息

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

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

    II. 其他

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

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

    声明

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

    扫描关注

    blogInfoV2.png

    相关文章

      网友评论

      本文标题:180530-反射获取泛型类的实际参数

      本文链接:https://www.haomeiwen.com/subject/idwcsftx.html