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. 簡單對比
- Type[] getGenericInterfaces
以Type的形式返回本類直接實現的接口.這樣就包含了泛型參數信息
- 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())
@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
掃描關註
180530-反射獲取泛型類的實際參數