文章連接: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