Java 反射(2):泛型相關周邊信息獲取

在上篇中,我們簡單給大家講解了如何利用反射來獲取普通類型的類的使用,今天給大家講解下,有關如何使用反射來獲取泛型中的信息。提前提個醒,本篇文章內容稍難,大家可能需要多看幾篇。
這篇文章將大量用到泛型的知識,如果對泛型聲明及填充不太瞭解的同學,請先看完《夯實JAVA基本之一 —— 泛型詳解系列》

一、獲取泛型超類和接口的相信信息

在這部分內容中,我們將講述如何獲取泛型的超類和接口,把上篇中遺留下來的兩個函數先講完。

1、獲取泛型超類相信信息

上篇中,我們講了,要獲取泛型類型的超類,要用到一個函數:

[java] view plaincopy
  1. //針對泛型父類而設計  
  2. public Type getGenericSuperclass();  
下面我們就先看看這個函數怎麼用,我們依然以上篇中的Point類以及它的派生類PointImpl爲例:
[java] view plaincopy
  1. //Point泛型類的實現  
  2. public class Point<T> {  
  3.     private T x,y;  
  4.   
  5.     public T getX() {  
  6.         return x;  
  7.     }  
  8.   
  9.     public void setX(T x) {  
  10.         this.x = x;  
  11.     }  
  12.   
  13.     public T getY() {  
  14.         return y;  
  15.     }  
  16.   
  17.     public void setY(T y) {  
  18.         this.y = y;  
  19.     }  
  20.   
  21. }  
  22. //PointImpl類的實現  
  23. public class PointImpl extends Point<Integer> {  
  24. }  
從上面的代碼中,我們可以看到,Point類是一個泛型類,具有一個泛型變量T;而PointImpl派生自Point並且在派生時,將Point進行填充爲Point,即將Point中的泛型變量填充爲Integer類型。
下面, 我們將通過反射獲取PointImpl的父類的類型,以及PointImpl的填充類型
我們在沒看代碼之前,我們先看看結果,我們知道PointImpl的父類類型是Point,而PointImpl的填充類型應該是Integer.
然後我們再看看代碼:
[java] view plaincopy
  1. Class<?> clazz = PointImpl.class;  
  2. Type type = clazz.getGenericSuperclass();  
  3. if (type instanceof ParameterizedType) {  
  4.     ParameterizedType parameterizedType = (ParameterizedType) type;  
  5.     //返回表示此類型實際類型參數的 Type 對象的數組  
  6.     Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
  7.     for (Type parameterArgType : actualTypeArguments) {  
  8.         Class parameterArgClass = (Class) parameterArgType;  
  9.         Log.d(TAG,"填充類型爲:" + parameterArgClass.getName());  
  10.     }  
  11.   
  12.     //返回 Type 對象,表示聲明此類型的類或接口。  
  13.     Type type1 = parameterizedType.getRawType();  
  14.     Class class22 = (Class) type1;  
  15.     Log.d(TAG,"PointImpl的父類類型爲:"+class22.getName());  
  16.   
  17. }  
相信上面這段代碼,大家肯定是很不懂的。。。。因爲確實狠複雜,不管那些,我們先看看結果:

從結果中,我們可以看到,先獲得到的是PointImpl在填充父類時的類型Integer,然後獲得的是PointImpl的父類類型。
下面先看如何獲取當前類在填充父類時的填充類型的:
對應代碼是這一塊:

[java] view plaincopy
  1. Class<?> clazz = PointImpl.class;  
  2. Type type = clazz.getGenericSuperclass();  
  3. if (type instanceof ParameterizedType) {  
  4.     ParameterizedType parameterizedType = (ParameterizedType) type;  
  5.     //返回表示此類型實際類型參數的 Type 對象的數組  
  6.     Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
  7.     for (Type parameterArgType : actualTypeArguments) {  
  8.         Class parameterArgClass = (Class) parameterArgType;  
  9.         Log.d(TAG,"填充類型爲:" + parameterArgClass.getName());  
  10.     }  
  11. }  

下面我們對這塊分塊講解:

(1)、獲取泛型超類

[java] view plaincopy
  1. Class<?> clazz = PointImpl.class;  
  2. Type type = clazz.getGenericSuperclass();  
在這段代碼中,我們通過clazz.getGenericSuperclass()獲取PointImpl.class的超類。由於我們知道PointImpl.class的父類是泛型,所以我們只能使用clazz.getGenericSuperclass()來獲取。但獲取出來的類型確是很讓人捉急。一個Type類型,下面我們先間斷下,講講這個Type類型是個什麼鬼。

(2)、Type類型

我們先看看Type的源碼,看他自己是怎麼說的:
[java] view plaincopy
  1. package java.lang.reflect;  
  2.   
  3. /** 
  4.  * Common interface implemented by all Java types. 
  5.  * 
  6.  * @since 1.5 
  7.  */  
  8. public interface Type {  
  9.     // Empty  
  10. }  
Type是一個接口,這裏意思是它是Java所有類型都會繼承這個接口。但通過源碼會發現String,Integer,Double這些類都沒有繼承這個接口,就連Object也沒繼承!
這就有點坑爹了,再仔細查代碼會出現,Class繼承了這個接口:
[java] view plaincopy
  1. public final class Class<T> implements Serializable, AnnotatedElement, GenericDeclaration, Type {  
  2. …………  
  3. }  
所以說,這個Type類型是泛型所特有的。那它用是來做什麼的呢?
他就是用來標識,當前Class中所填充的類型的。意思是,當我們在填充一個泛型時,比如上面我們的:
[java] view plaincopy
  1. public class PointImpl extends Point<Integer> {  
  2. }  
這個填充類型就會放在Type的保存起來,當需要用到的時候再取出來。那問題又來了,我們這裏填充的是Integer類型,那如果我們填充的是數組泛型呢,比如Point<ArrayList>,再假如我們填充的是一個通配符呢?這Type要怎麼識別呢?
爲了解決這個問題,Java的開發者,在Type的基礎上派生了另外幾個接口,分別來保存不同的類型,他們分別是:
  • ParameterizedType:這就是上面我們代碼中用到的,他代表的是一個泛型類型,比如Point,它就是一個泛型類型。
    我們在代碼中,利用:
    [java] view plaincopy
    1. Class<?> clazz = PointImpl.class;  
    2. Type type = clazz.getGenericSuperclass();  
    獲得PointImpl.class的父類,而它的父類是Point,這明顯是一個泛型類型,所以它對應的類型就是ParameterizedType;
  • TypeVariable:這個代表的就是泛型變量,例如Point,這裏面的T就是泛型變量,而如果我們利用一種方法獲得的對象是T,那它對應的類型就是TypeVariable;(這個類型的應用後面會細講)
  • WildcardType:上面的TypeVariable對應的是泛型變量,而如果我們得到不是泛型變量,而是通配符比如:? extends Integer,那它對應的類型就是WildcardType;
  • GenericArrayType:如果我們得到的是類似String[]這種數組形式的表達式,那它對應的類型就是GenericArrayType,非常值得注意的是如果type對應的是表達式是ArrayList這種的,這個type類型應該是ParameterizedType,而不是GenericArrayType,只有類似Integer[]這種的纔是GenericArrayType類型。
  • 雖然我們後面會對TypeVariable,WildcardType進行講解,這裏還是先對他們三個類型對應的意義先總結一下,比如我們這裏的clazz.getGenericSuperclass(),得到的Type對應的是完整的泛型表達式即:Point,那它對應的類型就是ParameterizedType,如果我們得到的Type對應的表達式,僅僅是Point中用來填充泛型變量T的Integer,那這個Type對應的類型就是TypeVariable,如果我們得到的是依然是填充泛型變量T的填充類型,這而個填充類型卻是通配符?,那這個Type對應的類型就是WildcardType。這一段看不大明白也沒關係,後面還會再講。

(3)、ParameterizedType

上面我們已經提到當獲取的Type類型,對應的是一個完整泛型表達式的時候,比如,我們這裏獲取到的PointImpl.class的父類:
[java] view plaincopy
  1. Class<?> clazz = PointImpl.class;  
  2. Type type = clazz.getGenericSuperclass();  
這時的type對應的完整表達式就是:Point
在ParameterizedType中有兩個極有用的函數:
[java] view plaincopy
  1. Type[] getActualTypeArguments();  
  2. Type getRawType();  
  • getActualTypeArguments():用來返回當前泛型表達式中,用來填充泛型變量的真正值的列表。像我們這裏得到的Point,用來填充泛型變量T的是Integer類型,所以這裏返回的Integer類型所對應的Class對象。(有關這一段,下面會補充,這裏先看getRawType)
  • getRawType():我們從我們上面的代碼中,也可以看到,它返回的值是com.harvic.blog_reflect_2.Point,所以它的意義就是聲明當前泛型表達式的類或者接口的Class對象。比如,我們這裏的type對應的是Point,而聲明Point這個泛型的當然是Point類型。所以返回的是Point.Class

下面我們再回過來看看getActualTypeArguments():
我們上面說到,這個函數將返回用來填充泛型變量真實參數列表。像我們這裏的是Point,將返回Integer對應的Class對象。而並不是所有的每次都會返回填充類型對應的Class對象。我們知道我們在填充一個泛型時,是存在各種可能的,比如Point,Point<? extends Number>,Point<ArrayList>,Point<ArrayList<? extend Number>>,等等
雖然我們沒辦法窮舉可能填充爲哪些類型,但我們知道Type類型是用來表示填充泛型變量的類型的,而繼承Type接口只有下面五個:Class,ParameterizedType,TypeVariable,WildcardType,GenericArrayType!
所以這也是Type[] getActualTypeArguments();中Type[]數組的所有可能取值!

(4)、言歸正轉
好了,現在我們再回來看看我們的代碼
[java] view plaincopy
  1. Class<?> clazz = PointImpl.class;  
  2. Type type = clazz.getGenericSuperclass();  
  3. if (type instanceof ParameterizedType) {  
  4.     ParameterizedType parameterizedType = (ParameterizedType) type;  
  5.     //返回表示此類型實際類型參數的 Type 對象的數組  
  6.     Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
  7.     for (Type parameterArgType : actualTypeArguments) {  
  8.         Class parameterArgClass = (Class) parameterArgType;  
  9.         Log.d(TAG,"填充類型爲:" + parameterArgClass.getName());  
  10.     }  
  11. }  
我們在Type type = clazz.getGenericSuperclass();之後,得到的type的值對應的是:Point,所以我們知道,type對應的是ParameterizedType,所以我們用
[java] view plaincopy
  1. if (type instanceof ParameterizedType) {  
  2. …………  
  3. }  
來識別,然後將type變量強轉爲ParameterizedType變量:
[java] view plaincopy
  1. ParameterizedType parameterizedType = (ParameterizedType) type;  
然後到了最重要的兩句:
[java] view plaincopy
  1. Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
  2. for (Type parameterArgType : actualTypeArguments) {  
  3.     Class parameterArgClass = (Class) parameterArgType;  
  4.     Log.d(TAG,"填充類型爲:" + parameterArgClass.getName());  
  5. }  
然後,先利用parameterizedType.getActualTypeArguments()獲取當前泛型變量的填充列表,我們知道Point中泛型變量T被填充爲Integer,所以我們得到的數組Type[]裏,只有一個值,它對應的就是Integer.Class。
然後我們將得到的Type進行強轉成Class類型,所以parameterArgClass對應的值就是Integer.Class。所以我們利用parameterArgClass.getName():java.lang.Integer

(5)、getRawType()

最後,我們再來看看getRawType的用法:
[java] view plaincopy
  1. Type type1 = parameterizedType.getRawType();  
  2. Class class22 = (Class) type1;  
  3. Log.d(TAG,"PointImpl的父類類型爲:"+class22.getName());  
我們知道,parameterizedType對應的值是Point,而parameterizedType.getRawType()得到的就是聲明這個泛型的類的Class對象。所以這裏的type1對應的值就是Point.Class。所以我們將其轉換成Class對象,通過class22.getName()得到的值是:com.harvic.blog_reflect_2.Point

2、獲取所繼承泛型接口的相關信息

上泛我們也說到,獲取普通類所繼承的接口使用的是Class.getInterfaces()函數,如果要獲取泛型接口的對象需要用到:
[java] view plaincopy
  1. //獲取泛型接口的方法  
  2. public Type[] getGenericInterfaces();  
這裏提前強調一點:大家需要注意是getGenericInterfaces()數與Class.getInterfaces()函數一樣,都只能獲取此類直接繼承的接口列表!
這裏得到的一個Type數組,因爲我們一個類可以繼承多個接口,所以這裏的每一個type對應的就是我們所繼承的一個接口類型。
下面我們舉個例子來看這個接口的用法:
首先,生成一個泛型接口:
[java] view plaincopy
  1. public interface PointInterface<T,U> {  
  2. }  
可以看到,我們這個泛型接口裏有兩個泛型變量,這個接口裏我們沒有定義任何的方法,因爲我們這裏只會獲取填充泛型接口的實際類型,不會用到它的方法,所以就沒有必要生成了,寫個空接口即可。
然後,我們直接使用前面的PointImpl來繼承好了,就不再另寫其它類了:
[java] view plaincopy
  1. public class PointImpl extends Point<Integer> implements PointInterface<String,Double> {  
  2. }  
從這裏可以看出,我們在生成PointImpl時將 PointInterface<T,U>填充爲PointInterface<String,Double>
下面我們來看如何來獲取PointImpl所繼承的泛型接口的信息:
[java] view plaincopy
  1. Class<?> clazz = PointImpl.class;  
  2. Type[] types = clazz.getGenericInterfaces();  
  3. for (Type type:types) {  
  4.     if (type instanceof ParameterizedType) {  
  5.         ParameterizedType parameterizedType = (ParameterizedType) type;  
  6.         //返回表示此類型實際類型參數的 Type 對象的數組  
  7.         Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
  8.         for (Type parameterArgType : actualTypeArguments) {  
  9.             Class parameterArgClass = (Class) parameterArgType;  
  10.             Log.d(TAG, "此接口的填充類型爲:" + parameterArgClass.getName());  
  11.         }  
  12.   
  13.         //返回 Type 對象,表示聲明此類型的類或接口。  
  14.         Type type1 = parameterizedType.getRawType();  
  15.         Class class22 = (Class) type1;  
  16.         Log.d(TAG,"聲明此接口的類型爲:"+class22.getName());  
  17.     }  
  18. }  
依然是一長段讓人受不了的代碼,我們一點點來分析。
首先,是獲得PointImpl.class所繼承接口的數組
[java] view plaincopy
  1. Class<?> clazz = PointImpl.class;  
  2. Type[] types = clazz.getGenericInterfaces();  
因爲我們知道,我們的PointImpl只繼承了一個接口:PointInterface<String,Double>,所以此時的Type[]中只有一個元素,即代表着PointInterface<String,Double>的type
然後是利用for…each循環遍歷types中的每一個元素。
[java] view plaincopy
  1. if (type instanceof ParameterizedType) {  
  2.     ParameterizedType parameterizedType = (ParameterizedType) type;  
  3.     //返回表示此類型實際類型參數的 Type 對象的數組  
  4.     Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
  5.     for (Type parameterArgType : actualTypeArguments) {  
  6.         Class parameterArgClass = (Class) parameterArgType;  
  7.         Log.d(TAG, "此接口的填充類型爲:" + parameterArgClass.getName());  
  8.     }  
  9.     …………  
因爲我們知道,我們這裏的type代表的是PointInterface<String,Double>,明顯它是一個泛型,所以它對應的type類型應該是ParameterizedType!下面的代碼就與上面獲取泛型超類的一樣了,即通過parameterizedType.getActualTypeArguments()獲取到它的參數數組
[java] view plaincopy
  1. Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
  2. for (Type parameterArgType : actualTypeArguments) {  
  3.     Class parameterArgClass = (Class) parameterArgType;  
  4.     Log.d(TAG, "此接口的填充類型爲:" + parameterArgClass.getName());  
  5. }  
因爲我們知道,PointInterface<T,U>被PointImpl填充爲PointInterface<String,Double>,所以它的真實的參數類型應該是String和Double, 我們前面說過Type只有五種類型:Class,ParameterizedType,TypeVariable,WildcardType,GenericArrayType。而ParameterizedType代表完整的泛型表達式,TypeVariable代表泛型變量的符號即T,U等,WildcardType代表通配符,GenericArrayType代表數組類型,而Class則表示派生於Object的所有Class類,明顯這裏的String和Double是Class類型的。
所以我們將它們強轉爲Class類型,然後通過parameterArgClass.getName()得到它們的完整路徑名。
最後通過parameterizedType.getRawType()獲取聲明PointInterface<String,Double>的接口類類型,雖然這裏得到的是Type,但我們聲明接口的是PointInterface.Class所以,也是Class類型,直接將其強轉爲Class即可。最後通過Class.getName()獲取其完整的路徑名。
[java] view plaincopy
  1. //返回 Type 對象,表示聲明此類型的類或接口。  
  2. Type type1 = parameterizedType.getRawType();  
  3. Class class22 = (Class) type1;  
  4. Log.d(TAG,"聲明此接口的類型爲:"+class22.getName());  
好了,到這裏,有關泛型超類和繼承接口的信息獲取到這就結束了,下面我們再來看看上面另外提到的另外三個Type類型:TypeVariable,WildcardType,GenericArrayType

二、Type的五種類型

上面說們說過,Type接口是用來保存當前泛型被填充的類型的,它總共有五種類型:Class,ParameterizedType,TypeVariable,WildcardType,GenericArrayType
在這上面的例子中,我們用到了Class,ParameterizedType;當type所代表的表達式是一個完整泛型時,比如Point,那這個Type類型就是ParameterizedType;如果type所代表的是一個確定的類,比如Integer,String,Double等,那這個type所對應的類型就是Class;強轉之後,得到的就是他們所對應的Class對象,即Integer.Class,String.Class,Double.Class等
前面我們說過,如果type對應的是一個泛型變量,即類似於T,或U這種還沒有被填充的泛型變量,那它的類型就是TypeVariable;而如果type對應的是一個通配符表達式,比如? extends Num,或者僅僅是一個通配符?,類似這種有通符符的類型就是WildcardType;
而如果type對應的類型是類似於String[]的數組,那它的類型就是GenericArrayType;
下面我們就來分別看看TypeVariable、WildcardType和GenericArrayType的用法

1、TypeVariable

我們上面說了,當type代表的類型是一個泛型變量時,它的類型就是TypeVariable。TypeVariable有兩個函數:
[java] view plaincopy
  1. String  getName();  
  2. Type[]  getBounds();  
  • getName:就是得到當前泛型變量的名稱;
  • getBounds:返回表示此類型變量上邊界的 Type 對象的數組。如果沒有上邊界,則默認返回Object;
有關這兩個函數我們舉個例子來詳細說明:
我們依然在PointInterface泛型接口上做文章:
[java] view plaincopy
  1. public interface PointInterface<T,U> {  
  2. }  
  3. public class PointGenericityImpl<T extends Number&Serializable> implements PointInterface<T,Integer> {  
  4. }  
這裏,我們在PointInterface的基礎上,重寫一個類PointGenericityImpl,與上面直接在類中填充不同的是,它是一個泛型類,首先,將PointInterface<T,U>填充爲PointInterface<T,Integer>,即第一個參數依然是一個泛型,而第二個參數填充爲Integer;而我們也給PointGenericityImpl中的泛型變量T添加了限定:T extends Number&Serializable,給它添加了extends限定(上邊界):指定T必須派生自Number類和Serializable類。
我們再看一下如何獲取信息:
[java] view plaincopy
  1. Class<?> clazz = PointGenericityImpl.class;  
  2. Type[] types = clazz.getGenericInterfaces();  
  3. for (Type type:types) {  
  4.     if (type instanceof ParameterizedType) {  
  5.         ParameterizedType parameterizedType = (ParameterizedType) type;  
  6.         //返回表示此類型實際類型參數的 Type 對象的數組  
  7.         Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
  8.         for (Type parameterArgType : actualTypeArguments) {  
  9.   
  10.             if(parameterArgType instanceof TypeVariable){  
  11.                 TypeVariable typeVariable = (TypeVariable) parameterArgType;  
  12.                 Log.d(TAG, "此接口的填充類型爲:" + typeVariable.getName());  
  13.   
  14.                 //返回表示此類型變量上邊界的 Type 對象的數組。  
  15.                 Type[] typebounds = typeVariable.getBounds();  
  16.                 for (Type bound:typebounds){  
  17.                     Class<?> boundClass = (Class)bound;  
  18.                     //如果不寫,則默認輸出Object,如果寫了,則輸出對應的  
  19.                     Log.d(TAG, "bound爲:" + boundClass.getName());  
  20.                 }  
  21.             }  
  22.             if (parameterArgType instanceof  Class){  
  23.                 Class parameterArgClass = (Class) parameterArgType;  
  24.                 Log.d(TAG, "此接口的填充類型爲:" + parameterArgClass.getName());  
  25.             }  
  26.         }  
  27.   
  28.     }  
  29. }  
先看看結果:

依然是一坨複雜得像翔一樣的代碼,我們逐段來分析下;
首先,獲取PointGenericityImpl直接繼承的的泛型接口數組

[java] view plaincopy
  1. Class<?> clazz = PointGenericityImpl.class;  
  2. Type[] types = clazz.getGenericInterfaces();  
我們知道PointGenericityImpl只直接繼承了一個接口:PointInterface<T,Integer>,其中T的限定爲:<T extends Number&Serializable>;所以types中只有一個元素,這個type元素代表的是PointInterface<T,Integer>,明顯它是一個泛型,所以這個type的類型是ParameterizedType。所以,我們下面雖然用了for …each進行了列舉了types中的所有元素,但我們知道它只有一個元素。
然後我們將這個元素強轉爲ParameterizedType,然後利用parameterizedType.getActualTypeArguments()得到PointInterface<T,U>中T和U被填充的真實類型對應的Type數組。
[java] view plaincopy
  1. ParameterizedType parameterizedType = (ParameterizedType) type;  
  2. //返回表示此類型實際類型參數的 Type 對象的數組  
  3. Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
我們知道,PointInterface<T,U>被真實填充爲了PointInterface<T,Integer>,其中T的限定爲:<T extends Number&Serializable>;所以這個Type[]數組包含兩個變量,一個是T,一個是Integer;
我們知道T是一個泛型變量,所以對應的類型應該是TypeVariable
而Integer則是一個具體的類,它對應的類型應該是Class

針對T:

[java] view plaincopy
  1. if(parameterArgType instanceof TypeVariable){  
  2.     TypeVariable typeVariable = (TypeVariable) parameterArgType;  
  3.     Log.d(TAG, "此接口的填充類型爲:" + typeVariable.getName());  
  4.   
  5.     //返回表示此類型變量上邊界的 Type 對象的數組。  
  6.     Type[] typebounds = typeVariable.getBounds();  
  7.     for (Type bound:typebounds){  
  8.         Class<?> boundClass = (Class)bound;  
  9.         //如果不寫,則默認輸出Object,如果寫了,則輸出對應的  
  10.         Log.d(TAG, "bound爲:" + boundClass.getName());  
  11.     }  
  12. }  
我們知道T對應的type是TypeVariable,所以將它強轉爲TypeVariable變量。然後用typeVariable.getName()獲取這個填充的泛型變量的名字,得到的值爲:

然後,利用typeVariable.getBounds()得到T的限定條件:上邊界的數組。上邊界的意思就是extends關鍵字後面的限定條件。“上”的意思就是能取到的最大父類。最大父類當然是用extends關鍵字來限定的。我們知道這裏的T的限定條件是:<T extends Number&Serializable>,所以 Type[] typebounds = typeVariable.getBounds();所得到typebounds有兩個變量,一個是Number,一個是Serializable;這兩個都是具體的類型,所以我們可以直接將它們轉換爲Class類型,然後利用Class.getName()獲取它們完整的路徑名,結果如下:(有關上下邊界的意義下面在講WildcardType時會有圖文講解)


針對Integer:

再來看下代碼:

[java] view plaincopy
  1. Class<?> clazz = PointGenericityImpl.class;  
  2. Type[] types = clazz.getGenericInterfaces();  
  3. for (Type type:types) {  
  4.     if (type instanceof ParameterizedType) {  
  5.         ParameterizedType parameterizedType = (ParameterizedType) type;  
  6.         //返回表示此類型實際類型參數的 Type 對象的數組  
  7.         Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
  8.         for (Type parameterArgType : actualTypeArguments) {  
  9.             …………  
  10.             if (parameterArgType instanceof  Class){  
  11.                 Class parameterArgClass = (Class) parameterArgType;  
  12.                 Log.d(TAG, "此接口的填充類型爲:" + parameterArgClass.getName());  
  13.             }  
  14.         }  
  15.   
  16.     }  
  17. }  
因爲PointInterface<T,U>被真實填充爲了PointInterface<T,Integer>,上面我們講完了T的獲取,在
[java] view plaincopy
  1. Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
中第一個類型是type類型對應的是T,第二個type類型則是Integer類型。明顯Integer是一個Class類型,所以我們直接將它強轉爲Class即可
[java] view plaincopy
  1. if (parameterArgType instanceof  Class){  
  2.    Class parameterArgClass = (Class) parameterArgType;  
  3.    Log.d(TAG, "此接口的填充類型爲:" + parameterArgClass.getName());  
  4. }  
結果爲:

2、GenericArrayType

上面我們說過,當type對應的類型是類似於String[]、Integer[]等的數組時,那type的類型就是GenericArrayType;這裏要特別說明的如果type對應的是類似於ArrayList、List這樣的類型,那type的類型應該是ParameterizedType,而不是GenericArrayType,因爲ArrayList是一個泛型表達式。所以當且僅當type對應的類型是類似於String[]、Integer[]這樣的數組時,type的類型纔是GenericArrayType!
我們先看看GenericArrayType的函數:

[java] view plaincopy
  1. Type getGenericComponentType();  
getGenericComponentType:這是GenericArrayType僅有一個函數,由於getGenericComponentType所代表的表達是String[]這種的數組,所以getGenericComponentType獲取的就是這裏的數組類型所對應的Type,比如這裏的String[]通過getGenericComponentType獲取到的Type對應的就是String.
好了,下面我們就舉個例子來看看GenericArrayType的用法
我們重新生成一個泛型接口PointSingleInterface:
[java] view plaincopy
  1. public interface PointSingleInterface<T> {  
  2. }  
這個泛型接口,只有一個泛型變量
然後生成一個類繼承這個接口:
[java] view plaincopy
  1. public class PointArrayImpl implements PointSingleInterface<Integer[]> {  
  2. }  
在PointArrayImpl中,我們填充PointSingleInterface中泛型變量T的是Integer[],一個Integer數組!
下面我們來看看如何獲取PointArrayImpl的接口信息:
[java] view plaincopy
  1. Class<?> clazz = PointArrayImpl.class;  
  2. Type[] interfaces = clazz.getGenericInterfaces();  
  3. for (Type type:interfaces){  
  4.     if (type instanceof ParameterizedType) {  
  5.         ParameterizedType pt = (ParameterizedType) type;  
  6.         Type[] actualArgs = pt.getActualTypeArguments();  
  7.         for (Type arg:actualArgs){  
  8.             if (arg instanceof GenericArrayType){  
  9.                 GenericArrayType arrayType = (GenericArrayType)arg;  
  10.                 Type comType = arrayType.getGenericComponentType();  
  11.                 Class<?> typeClass = (Class)comType;  
  12.                 Log.d(TAG,"數組類型爲:"+typeClass.getName());  
  13.             }  
  14.         }  
  15.     }  
  16. }  
先看執行結果:

依然看起來有點複雜,我們一點點來分析:
首先,通過clazz.getGenericInterfaces()獲取PointArrayImpl.class的接口對應的type列表

[java] view plaincopy
  1. Class<?> clazz = PointArrayImpl.class;  
  2. Type[] interfaces = clazz.getGenericInterfaces();  
我們知道PointArrayImpl.class只直接繼承一個接口:PointSingleInterface<Integer[]>,所以interfaces數組中只有一個元素,它代表的表達式就是PointSingleInterface<Integer[]>;明顯這個一個泛型表達式,所以這個type的類型就是ParameterizedType
[java] view plaincopy
  1. for (Type type:interfaces){  
  2.     if (type instanceof ParameterizedType) {  
  3.         ParameterizedType pt = (ParameterizedType) type;  
  4.         Type[] actualArgs = pt.getActualTypeArguments();  
  5.         for (Type arg:actualArgs){  
  6.            …………  
  7.         }  
  8.     }  
  9. }  
然後利用for…each對interfaces數組進行逐個列表,但我們知道它只有一個元素,代表的表達式是PointSingleInterface<Integer[]>;然後用
[java] view plaincopy
  1. Type[] actualArgs = pt.getActualTypeArguments()  
得到表達式中PointSingleInterface<Integer[]>的參數列表,顯然參數只有一個,即Integer[],所以actualArgs中只有一個元素,這個type元素對應的表達式是Integer[];
我們知道當type對應的表達式是Integer[]時,這個type的類型就是GenericArrayType
[java] view plaincopy
  1. if (arg instanceof GenericArrayType){  
  2.     GenericArrayType arrayType = (GenericArrayType)arg;  
  3.     Type comType = arrayType.getGenericComponentType();  
  4.     Class<?> typeClass = (Class)comType;  
  5.     Log.d(TAG,"數組類型爲:"+typeClass.getName());  
  6. }  
我們將arg強轉爲GenericArrayType類型的變量arrayType,然後利用arrayType.getGenericComponentType()得到數組的類型,因爲我們這裏的數組是Integer[],所以得到的類型是Integer,明顯這是一個確切的類,所以它的類型就是Class,所以我們直接將comType進行強制轉換爲Class<?> typeClass,最後利用typeClass.getName()得到Integer的具體類名。
好了,到這裏,我們已經講完了三種類型,下面開始講解最後一個也是最難的一種類型WildcardType!

3、WildcardType

(1)、概述

我們前面說過,當type所代表的表達式是類型通配符相關的表達式時,比如<? extends Integer>,<? super String>,或者<?>等,這個type的類型就是WildcardType!
我們先來看看WildcardType的函數:
[java] view plaincopy
  1. //獲取上邊界對象列表  
  2. Type[] getUpperBounds();  
  3. //獲取下邊界對象列表  
  4. Type[] getLowerBounds();  
  • getUpperBounds:獲取上邊界對象列表,上邊界就是使用extends關鍵定所做的的限定,如果沒有默認是Object;
  • getLowerBounds:獲取下邊界對象列表,下邊界是指使用super關鍵字所做的限定,如果沒有,則爲Null
我們舉個例子:
<? extends Integer>:這個通配符的上邊界就是Integer.Class,下邊界就是null
<? super String>:這個通配符的下邊界是String,上邊界就是Object;
有關上下邊界,大家可能很不好記,我畫個圖來給大家解釋下,上下邊界的含義:

看到這個類繼承圖,大家應該很容易就明白了,類繼承圖中,根結點始終是在祖先類,而且在繼承圖的上方,所以上方的就是上界,而子類是在下方,下方的就是下界。
而表現在代碼上,上界是繼承的關係,所以是<? extends Object>,而下界的則是<? super Double>

(2)、有關通配符的使用範圍

我們在《夯實JAVA基本之一——泛型詳解(2)》講過,通配符只是泛型變量的填充類型的一種,不能做爲泛型變量使用。
《夯實JAVA基本之一——泛型詳解(2)》中我們多次強調:通配符?只能出現在Box<?> box;中,其它位置都是不對的。
即只能出現在生成泛型實例時使用,其它位置都是不可以的。
尤其像下面這兩個,直接用來填充類中的泛型變量:



但下面這樣卻是允許的:


因爲,這裏的通配符Comparable<? extends Number>,只有來生成Comparable對象的,所以是允許使用的!大家一定要注意,通配符只能用來填充泛型類來生成對象。其它用途一概是錯誤的!

(3)、舉個例子

同樣,我們使用上面的PointSingleInterface泛型接口:

[java] view plaincopy
  1. public interface PointSingleInterface<T> {  
  2. }  
然後我們生成一個類來繼承這個接口:
[java] view plaincopy
  1. public class PointWildcardImpl implements PointSingleInterface<Comparable<? extends Number>> {  
  2. }  
接下來就看看我們是怎麼得到的PointWildcardImpl信息的:(代碼比較長,後面會細講)
[java] view plaincopy
  1. Class<?> clazz = PointWildcardImpl.class;  
  2. //此時的type對應PointSingleInterface<Comparable<? extends Number>>  
  3. Type[] types = clazz.getGenericInterfaces();  
  4. for (Type type:types) {  
  5.     if (type instanceof ParameterizedType) {  
  6.         ParameterizedType parameterizedType = (ParameterizedType) type;  
  7.         //得到填充PointSingleInterface的具體參數,即:Comparable<? extends Number>,仍然是一個ParameterizedType  
  8.         Type[] actualTypes = parameterizedType.getActualTypeArguments();  
  9.         for (Type actualType : actualTypes) {  
  10.             if (actualType instanceof ParameterizedType) {  
  11.                 ParameterizedType ComparableType = (ParameterizedType) actualType;  
  12.                 //對Comparable<? extends Number>再取填充參數,得到的type對應<? extends Number>,這個就是WildcardType了  
  13.                 Type[] compareArgs = ComparableType.getActualTypeArguments();  
  14.                 for (Type Arg:compareArgs){  
  15.                     if(Arg instanceof WildcardType){  
  16.                         //將得到的對應WildcardType的type強轉爲WildcardType的變量  
  17.                         WildcardType wt = (WildcardType) Arg;  
  18.   
  19.                         //利用getLowerBounds得到下界,即派生自Super的限定,如果沒有派生自super則爲null  
  20.                         Type[] lowerBounds = wt.getLowerBounds();  
  21.                         for (Type bound:lowerBounds){  
  22.                             Class<?> boundClass = (Class)bound;  
  23.                             Log.d(TAG, "lowerBound爲:" + boundClass.getName());  
  24.                         }  
  25.   
  26.                         //通過getUpperBounds得到上界,即派生自extends的限定,如果沒有,默認是Object  
  27.                         Type[] upperBounds = wt.getUpperBounds();  
  28.                         for (Type bound:upperBounds){  
  29.                             Class<?> boundClass = (Class)bound;  
  30.                             //如果不寫,則默認輸出Object,如果寫了,則輸出對應的  
  31.                             Log.d(TAG, "upperBound爲:" + boundClass.getName());  
  32.                         }  
  33.   
  34.                     }  
  35.                 }  
  36.             }  
  37.         }  
  38.   
  39.     }  
  40. }  
執行結果爲:

看到這一大段代碼,估計尿血的感覺都出來了。我們對它逐段分析:
首先獲取PointWildcardImpl.class所直接繼承的接口

[java] view plaincopy
  1. Class<?> clazz = PointWildcardImpl.class;  
  2. Type[] types = clazz.getGenericInterfaces();  
所以此時types中唯一的一個元素type所代表的表達式是:PointSingleInterface<Comparable<? extends Number>>,明顯這是一個泛型,所以type類型是ParameterizedType
[java] view plaincopy
  1. ParameterizedType parameterizedType = (ParameterizedType) type;  
  2. Type[] actualTypes = parameterizedType.getActualTypeArguments();  
然後將type強轉成ParameterizedType的變量,利用parameterizedType.getActualTypeArguments()獲取PointSingleInterface的填充參數:Comparable<? extends Number>
所以此時的actualTypes也僅有一個元素,它代表的表達式是Comparable<? extends Number>,明顯這依然是一個泛型,所以還得繼續往下剝
所以繼續將其中的actualType進行強轉,然後再得到它的填充參數:
[java] view plaincopy
  1. ParameterizedType ComparableType = (ParameterizedType) actualType;  
  2. Type[] compareArgs = ComparableType.getActualTypeArguments();  

此時的compareArgs列表中也僅有一個元素,這個元素代表的表達式是:<? extends Number>
這就是一個WildcardType類型了,然後是得到這個WildcardType的上下邊界信息了:
完整獲取上下邊界的代碼如下:(後面會分開講)

[java] view plaincopy
  1. for (Type Arg:compareArgs){  
  2.     if(Arg instanceof WildcardType){  
  3.         //將得到的對應WildcardType的type強轉爲WildcardType的變量  
  4.         WildcardType wt = (WildcardType) Arg;  
  5.   
  6.         //利用getLowerBounds得到下界,即派生自Super的限定,如果沒有派生自super則爲null  
  7.         Type[] lowerBounds = wt.getLowerBounds();  
  8.         for (Type bound:lowerBounds){  
  9.             Class<?> boundClass = (Class)bound;  
  10.             Log.d(TAG, "lowerBound爲:" + boundClass.getName());  
  11.         }  
  12.   
  13.         //通過getUpperBounds得到上界,即派生自extends的限定,如果沒有,默認是Object  
  14.         Type[] upperBounds = wt.getUpperBounds();  
  15.         for (Type bound:upperBounds){  
  16.             Class<?> boundClass = (Class)bound;  
  17.             //如果不寫,則默認輸出Object,如果寫了,則輸出對應的類名  
  18.             Log.d(TAG, "upperBound爲:" + boundClass.getName());  
  19.         }  
  20.   
  21.     }  
  22. }  
回過頭來先看如何得到下邊界:
[java] view plaincopy
  1. WildcardType wt = (WildcardType) Arg;  
  2.   
  3. //利用getLowerBounds得到下界,即派生自Super的限定,如果沒有派生自super則爲null  
  4. Type[] lowerBounds = wt.getLowerBounds();  
  5. for (Type bound:lowerBounds){  
  6.     Class<?> boundClass = (Class)bound;  
  7.     Log.d(TAG, "lowerBound爲:" + boundClass.getName());  
  8. }  
我們講到,下邊界使用的是super關鍵字來做限定的,我們這裏的表達式是:<? extends Number>,所以下邊界是Null,雖然寫了代碼,也不會執行到裏面去。
然後再看看如何得到上邊界:
[java] view plaincopy
  1. Type[] upperBounds = wt.getUpperBounds();  
  2. for (Type bound:upperBounds){  
  3.     Class<?> boundClass = (Class)bound;  
  4.     //如果不寫,則默認輸出Object,如果寫了,則輸出對應的類名  
  5.     Log.d(TAG, "upperBound爲:" + boundClass.getName());  
  6. }  
我們知道,<? extends Number>的下邊界就是Number.Class,所以Type[] upperBounds數組中只有一個元素,這個type對應的是Number.Class,所以將它強轉爲Class對象,然後通過boundClass.getName()得到它的完整對象名。
結果如下:

好了,到這裏,所有有關type的類型就講完了,但我們上面是逐個分析當前type應該強轉爲哪種類型的,如果我們稍微疏忽分析錯了,或者,我們根本不知道它當前是哪種類型,這要怎麼辦,我們必須能寫出來一個統一的轉換函數出來!我們知道type所有的類型總共五種:Class,ParameterizedType,TypeVariable,WildcardType,GenericArrayType;所以我們利用遞規的方法來寫一個通用類型轉換函數出來。

4、Demo:寫一個通用類型轉換函數

我們這節就要寫一個通用的類型轉換函數了,

1、實現parseClass(Class<?> c)函數

先寫一個parseClass的入口函數,用來得到他所直接繼承的泛型接口:

[java] view plaincopy
  1. private void parseClass(Class<?> c){  
  2.     parseTypeParameters(c.getGenericInterfaces());  
  3. }  
然後再來實現它其中的parseTypeParameters函數,來解析type[]數組:
[java] view plaincopy
  1. private void parseTypeParameters(Type[] types){  
  2.     for(Type type:types){  
  3.         parseTypeParameter(type);  
  4.     }  
  5. }  
這段代碼也很簡單,對types進行枚舉,利用parseTypeParameter(type)對每一個type進行分析,我們看看parseTypeParameter(type)是如何對每一個函數進行分析的:
[java] view plaincopy
  1. private void parseTypeParameter(Type type){  
  2.     if(type instanceof Class){  
  3.         Class<?> c = (Class<?>) type;  
  4.         Log.d(TAG, c.getSimpleName());  
  5.     } else if(type instanceof TypeVariable){  
  6.         TypeVariable<?> tv = (TypeVariable<?>)type;  
  7.         Log.d(TAG, tv.getName());  
  8.         parseTypeParameters(tv.getBounds());  
  9.     } else if(type instanceof WildcardType){  
  10.         WildcardType wt = (WildcardType)type;  
  11.         Log.d(TAG, "?");  
  12.         parseTypeParameters(wt.getUpperBounds());  
  13.         parseTypeParameters(wt.getLowerBounds());  
  14.     } else if(type instanceof ParameterizedType){  
  15.         ParameterizedType pt = (ParameterizedType)type;  
  16.         Type t = pt.getOwnerType();  
  17.         if(t != null) {  
  18.             parseTypeParameter(t);  
  19.         }  
  20.         parseTypeParameter(pt.getRawType());  
  21.         parseTypeParameters(pt.getActualTypeArguments());  
  22.     } else if (type instanceof GenericArrayType){  
  23.         GenericArrayType arrayType = (GenericArrayType)type;  
  24.         Type t = arrayType.getGenericComponentType();  
  25.         parseTypeParameter(t);  
  26.     }  
  27. }  
這段代碼很長,我們先不管它是怎麼寫出來的,下面再細講,我們先看看如何使用我們寫出來的parseClass(Class<?> c)函數來解析的;

2、使用parseClass(Class<?> c)

首先,我們還用上面用PointWildcardImpl類:
[java] view plaincopy
  1. public class PointWildcardImpl implements PointSingleInterface<Comparable<? extends Number>> {  
  2. }  
然後我們調用parseClass(Class<?> c)對它進行分析:
[java] view plaincopy
  1. parseClass(PointWildcardImpl.class);  
結果爲:

可以看到,打印出了每一個字段的名字。
好了,我們再回過頭來看看,parseTypeParameter(Type type)是怎麼寫的。

3、parseClass(Class<?> c)實現詳解

在parseClass(Class<?> c)中,最關鍵的部分是parseTypeParameter(Type type)函數,所以我們直接對parseTypeParameter(Type type)進行分析。
我們知道,type總共有五種類型:Class,ParameterizedType,TypeVariable,WildcardType,GenericArrayType,所以我們在解析type時分別對它的每種類型進行判斷,然後分別解析即可:

[java] view plaincopy
  1. if(type instanceof Class){  
  2.     Class<?> c = (Class<?>) type;  
  3.     Log.d(TAG, c.getSimpleName());  
  4. }   
如果type是Class類型,則說明type是一個確切的類了,不會再有下一層的泛型參數填充了,所以直接打印出它的名字。
[java] view plaincopy
  1. else if(type instanceof TypeVariable){  
  2.     TypeVariable<?> tv = (TypeVariable<?>)type;  
  3.     Log.d(TAG, tv.getName());  
  4.     parseTypeParameters(tv.getBounds());  
  5. }   
如果type是一個泛型變量,即類似於T,U這些代表泛型變量的字母,我們先打印出這個字母,然後由於泛型變量還有邊界,tv.getBounds()可以得到它的所有上邊界。所以利用parseTypeParameters(tv.getBounds());分析它的所有上邊界type
[java] view plaincopy
  1. else if(type instanceof WildcardType){  
  2.     WildcardType wt = (WildcardType)type;  
  3.     Log.d(TAG, "?");  
  4.     parseTypeParameters(wt.getUpperBounds());  
  5.     parseTypeParameters(wt.getLowerBounds());  
  6. }   
如果type類型是WildcardType,即上面最難的通配符,因爲只要是WildcardType,肯定是有問號的,但WildcardType是沒有getName()函數來獲取問號的標識的,所以我們只有手動輸出一個問號標識了Log.d(TAG, “?”);同樣通配符也是有上下邊界的,比如<? extends xxx>,<? super xxx>,所以利用parseTypeParameters()分別來分析它的上邊界type數組和下邊界的type數組
[java] view plaincopy
  1. else if(type instanceof ParameterizedType){  
  2.     ParameterizedType pt = (ParameterizedType)type;  
  3.     parseTypeParameter(pt.getRawType());  
  4.     parseTypeParameters(pt.getActualTypeArguments());  
  5. }  
如果type的類型是ParameterizedType,那type代表的表達式肯定是一個泛型,類似於我們上面的Comparable<? extends Number>,我們利用pt.getRawType()得到聲明這個類的類型,比如這裏的Comparable<? extends Number>,得到的將是Comparable.Class。然後利用getActualTypeArguments()得到這個泛型的具體填充參數列表。同樣利用parseTypeParameter()和parseTypeParameters()再次分析。
[java] view plaincopy
  1. else if (type instanceof GenericArrayType){  
  2.     GenericArrayType arrayType = (GenericArrayType)type;  
  3.     Type t = arrayType.getGenericComponentType();  
  4.     parseTypeParameter(t);  
  5. }  
最後,是GenericArrayType類型,如果type代表的表達式是類似於String[],Integer[]這種數組的話,那type就是GenericArrayType類型。GenericArrayType只有一個函數getGenericComponentType(),得到這個數組的類型。同樣將返回的type值傳給parseTypeParameter(t)再次分析。

好了,有關反射中相關泛型的部分就結束了,下面再總結一下,本篇文章中所涉及到的所有函數。

5、本文涉及函數:

[java] view plaincopy
  1. //獲取泛型超類的Type  
  2. public Type getGenericSuperclass();  
  3. //獲取泛型接口的方法  
  4. public Type[] getGenericInterfaces();  
ParameterizedType相關
[java] view plaincopy
  1. //獲取填充泛型變量的真實參數列表  
  2. Type[] getActualTypeArguments();  
  3. //返回聲明此當前泛型表達式的類或接口的Class對象  
  4. Type getRawType();  
TypeVariable相關
[java] view plaincopy
  1. //就是得到當前泛型變量的名稱;  
  2. String  getName();  
  3. //返回表示此類型變量上邊界的 Type 對象的數組。如果沒有上邊界,則默認返回Object;  
  4. Type[]  getBounds();  
GenericArrayType相關
[java] view plaincopy
  1. //當前數組類型所對應的Type  
  2. Type getGenericComponentType();  
WildcardType相關
[java] view plaincopy
  1. //獲取通配符的上邊界對象列表  
  2. Type[] getUpperBounds();  
  3. //獲取通配符的下邊界對象列表  
  4. Type[] getLowerBounds();  
到這裏整篇文章也就結束了,泛型相關的這部分東西也真是太難講了,相當複雜而且不易理解,大家有用到的話就多看幾遍吧,下篇我們將會講到如何利用反射得到類的內部信息。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章