1. 程式人生 > >180530-反射獲取泛型類的實際參數

180530-反射獲取泛型類的實際參數

sys party params 情況下 imp upload sbo 如果 type()

文章鏈接: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. 其他

一灰灰Blog: https://liuyueyi.github.io/hexblog

一灰灰的個人博客,記錄所有學習和工作中的博文,歡迎大家前去逛逛

聲明

盡信書則不如,已上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

  • 微博地址: 小灰灰Blog
  • QQ: 一灰灰/3302797840

掃描關註

技術分享圖片

180530-反射獲取泛型類的實際參數