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

文章链接: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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容