反射與泛型

原文: https://lsy.iteye.com/blog/220264

反射是Java語言中很重要的一個組成部分市栗,所以就此話題討論的資源可謂數(shù)之不盡维哈,日常開發(fā)也會經(jīng)常使用到關(guān)于反射的 Reflection API 。
Java5.0 Tiger 出現(xiàn)以后刻蚯,更對反射API有了新的擴(kuò)展埂软,盡管討論的話題很多市殷,不過我還是覺得不夠全面勘究,尤其是對泛型這一塊矮湘,所以就我所知,再花力氣總結(jié)一番

首先反射的入口是從 Class 開始的口糕,所以如何獲取 Class 就變得十分關(guān)鍵了缅阳。這里總結(jié)了幾種方式:

  1. 通過 ${name}.class 語法。這里${name}可以是對象景描,也可以是原始數(shù)據(jù)類型券时,不過別忘了 void.classVoid.class
  2. 通過 ${name}.TYPE 語法。這里${name}是八種原始數(shù)據(jù)的包裝類和Void.TYPE
  3. 通過對象的 getClass() 方法伏伯。
  4. 通過 Class 對象的 forName() 方法
  5. 通過類 Class 的 getSuperclass() 獲取父親類Class
  6. 通過類 Class 的 getEnclosingClass() 獲取外部類Class
  7. 通過類 Class 的 getClasses()getDeclaredClasses() 獲取內(nèi)部類Class

下面是一張表用來說明 getClasses()getDeclaredClasses() 兩個方法,稍后還會用該表說明其他 Reflection API


Member Class API Return type Inherited members Private members
Class getDeclaredClasses() Array N Y
getClasses() Array Y N
Field getDeclaredField() Single N Y
getField() Single Y N
getDeclaredFields() Array N Y
getFields() Array Y N
Method getDeclaredMethod() Single N Y
getMethod() Single Y N
getDeclaredMethods() Array N Y
getMethods() Array Y N
Constructor getDeclaredConstructor() Single N/A Y
getConstructor() Single N/A N
getDeclaredConstructors() Array N/A Y
getConstructors() Array N/A N

如上表所示捌袜, getClasses() 擁有繼承的特點说搅,可以獲取父親級定義的內(nèi)部類,而不能訪問定義為private的內(nèi)部類虏等;
而 getDeclaredClasses() 剛好相反弄唧,可以訪問定義為 private 的內(nèi)部類,卻無法獲取父親級定義的內(nèi)部類

成功獲取了Class以后霍衫,那么就可以開始訪問 Field, Method 和 Constructor 了候引,他們都繼承自 java.lang.reflect.Member 。
從上表已經(jīng)很容易可以看出各個成員是否擁有繼承特性敦跌,是否能夠訪問私有成員澄干,返回類型的數(shù)量這些信息。
這里需要注意一點柠傍,由于 Constructor 是無法被繼承的麸俘,所以 Constructor 成員任何方法都沒有繼承的特性。
另外 Field 和 Method 在賦值或者調(diào)用的之前需要留意是否在操作私有成員惧笛,如果是那么需要先修改可訪問度从媚,執(zhí)行 setAccessible(true) 。
還有一點就是 Method 成員的 getDeclaredMethod() 和 getMethod() 方法都需要兩個參數(shù)患整,一個是方法的名稱拜效,另外一個參數(shù) Class 的數(shù)組,這里需要感謝 Java5.0 引入不定長參數(shù)的特點各谚,使我們可以在某些情況下少傳入一個參數(shù)紧憾,如:

假設(shè)Order類有下列方法
public Long getId() { return id; }

5.0以前獲取該方法的代碼如下
Method getId = Order.class.getMethod("getId", new Class[0]);

而5.0僅需要寫
Method getId = Order.class.getMethod("getId");

現(xiàn)在說說5.0泛型出現(xiàn)之后, Java Reflection API 的新特點昌渤。
首先增加一個接口 java.lang.reflect.Type 稻励,其下一共有4個接口繼承了它, TypeVariable, ParameterizedType, GenericArrayType 和 WildcardType,下面逐個分析望抽。

1.TypeVariable

我們知道泛型信息會在編譯時被JVM編譯時轉(zhuǎn)換為定義的一個特定的類型加矛,這減少了應(yīng)用程序逐步向下檢查類型的開支,避免了發(fā)生 ClassCastException 的危險煤篙。
而 TypeVariable 就是用來反映在JVM編譯該泛型前的信息斟览。舉個例子,假設(shè) BaseOrder 類定義有如下一個方法

    public class BaseOrder<M extends Object & Serializable, N extends Comparable<N>> implements IBaseOrder {  
        public M getManufactory(){  
            return manufactory;  
        }  
    }  

這時候我們可以通過如下代碼獲取該泛型 Type , 并且經(jīng)過測試該 Type 就是 TypeVariable

    Field manufactoryField = BaseOrder.class.getDeclaredField("manufactory");     
    type = manufactoryField.getGenericType();  
    assertTrue("The type of field manufactory is an instance of TypeVariable", type instanceof TypeVariable);  
    TypeVariable tType = (TypeVariable)type;  
    assertEquals("The name of this TypeVariable is M", "M", tType.getName());  
    assertEquals("The TypeVariable bounds two type", 2, tType.getBounds().length);   
    assertEquals("One type of these bounds is Object", Object.class, tType.getBounds()[0]);   
    assertEquals("And annother si Serializable", Serializable.class, tType.getBounds()[1]);  

通過 getName() 方法可以獲取該泛型定義的名稱辑奈,而更為重要的是 getBounds() 方法苛茂,可以判斷該泛型的邊界。

2.ParameterizedType

這個接口就比較出名了鸠窗,在過去討論最多的問題就是 GenericDao<T> 中妓羊,如何獲取 T.class 的問題了。這里再翻出來過一遍稍计,加入上述的類B aseOrder 定義不變躁绸,新定義一個Order對象,代碼如下:

    public class Order extends BaseOrder<Customer, Long> implements IOrder, Serializable {  
    }  

那么如何通過 Order 獲取到 Customer 呢臣嚣?

    Type genericSuperclass = Order.class.getGenericSuperclass();  
    assertTrue("Order's supper class is a type of ParameterizedType.", genericSuperclass instanceof ParameterizedType);  
    ParameterizedType pType = (ParameterizedType)genericSuperclass;  
    assertEquals("Order's supper class is BaseOrder.", BaseOrder.class, pType.getRawType());  
    Type[] arguments = pType.getActualTypeArguments();  
    assertEquals("getActualTypeArguments() method return 2 arguments.", 2, arguments.length);  
    for (Type type : arguments) {  
        Class clazz = (Class)type;  
        if(!(clazz.equals(Customer.class)) && !(clazz.equals(Long.class))){  
            assertTrue(false);  
        }  
    }

可以看出通過Order類的 getGenericSuperclass() 方法將返回一個泛型净刮,并且它就是 ParameterizedType 。這個接口的 getRawType() 方法和 getActualTypeArguments() 都非常重要.
getRawType() 方法返回的是承載該泛型信息的對象硅则,而 getActualTypeArguments() 將會返回一個實際泛型對象的數(shù)組淹父。
這里先提及一下Class對象中 getGenericSuperclass() 和 getSuperclass() 兩個方法的區(qū)別,后文還有詳細(xì)說明怎虫。
getGenericSuperclass() 方法首先會判斷是否有泛型信息暑认,有那么返回泛型的 Type ,沒有則返回 Class 大审,方法的返回類型都是 Type 穷吮,這是因為 Tiger 中 Class 也實現(xiàn)了 Type 接口。將父親按照 Type 接口的形式返回饥努,而 getSuperclass() 直接返回父親的 Class 捡鱼。

3.GenericArrayType

這個接口比較好理解。如果泛型參數(shù)是一個泛型的數(shù)組酷愧,那么泛型 Type 就是 GenericArrayType 驾诈,它的 getGenericComponentType() 將返回被JVM編譯后實際的數(shù)組對象。這里假設(shè)上文中BaseOrder有一個方法如下:

    public String[] getPayments(String[] payments, List<Product> products){  
        return payments;  
    }  

可以看出該方法的參數(shù)中有泛型信息溶浴,測試一下:

    Method getPayments = BaseOrder.class.getMethod("getPayments", new Class[]{String[].class, List.class});  
    types = getPayments.getGenericParameterTypes();  
    assertTrue("The first parameter of this method is GenericArrayType.", types[0] instanceof GenericArrayType);  
    GenericArrayType gType = (GenericArrayType)types[0];  
    assertEquals("The GenericArrayType's component is String.", String.class, gType.getGenericComponentType());  

發(fā)現(xiàn)這個 getPayments() 方法中的一個參數(shù) String[] payments 是一個 GenericArrayType 乍迄,通過 getGenericComponentType() 方法返回的是 String.class 。這是怎么回事呢士败?
這里我們回過頭去看 Class 對象的 getGenericSuperclass() 方法和 getSuperclass() 方法闯两,如果把它們說成是一對的話褥伴,那么這里的 getGenericParameterTypes() 和 getParameterTypes() 就是另外一對。
也就是說 getGenericParameterTypes() 首先判斷該方法的參數(shù)中是否有泛型信息漾狼,有那么返回泛型Type的數(shù)組重慢,沒有那么直接按照Class的數(shù)組返回;
而getParameterTypes()就直接按照Class的數(shù)組返回逊躁。非常相似吧似踱,其原因就是這些成對的方法都有一個共同點就是判斷是否有泛型信息,可以查看 Tiger 的源代碼:

    public Type[] getGenericParameterTypes() {  
        if (getGenericSignature() != null)  
            return getGenericInfo().getParameterTypes();  
        else 
            return getParameterTypes();  
    }  

而這類成對出現(xiàn)的方法還很多稽煤,如Method對象定義的 getGenericReturnType() 和 getReturnType() 核芽, getGenericExceptionTypes() 和 getExceptionTypes() ,
Field對象定義的 getGenericType() 和 getType() 酵熙。

4.WildcardType

這個接口就是獲取通配符泛型的信息了轧简。這里假設(shè)上述的BaseOrder定義有一個屬性

    private Comparable<? extends Customer> comparator;  

現(xiàn)在就來獲取泛型?的信息,測試代碼如下:

    Field comparatorField = BaseOrder.class.getDeclaredField("comparator");       
    ParameterizedType pType = (ParameterizedType)comparatorField.getGenericType();  
    type = pType.getActualTypeArguments()[0];  
    assertTrue("The type of field comparator is an instance of ParameterizedType, and the actual argument is an instance of WildcardType.", type instanceof WildcardType);  
    WildcardType wType = (WildcardType)type;  
    assertEquals("The upper bound of this WildcardType is Customer.", Customer.class, wType.getUpperBounds()[0]);  

首先我們獲取到 comparator 這個屬性匾二,通過它的 getGenericType() 方法我們拿到了一個Type哮独,可以看出它是一個 ParameterizedType ,而 ParameterizedType 的 actual argument 就是我們需要的 WildcardType 假勿,
這個接口有兩個主要的方法, getLowerBounds() 獲取該通配符泛型的下界信息态鳖,相反 getUpperBounds() 方法獲取該通配符泛型的上界信息转培。

說完了這四個接口,我們再回過頭來看看Method對象浆竭,它還定義有一個方法 getTypeParameters() 浸须,這又是干什么的呢?我們知道泛型是不能出現(xiàn)在靜態(tài)的成員邦泄,靜態(tài)的方法删窒,或者靜態(tài)的初始化器的邏輯中的。如下列代碼都是錯誤的:

    //error  
    private static T customer;   
    //error  
    public static T getCustomer(){  
        return customer;  
    }  
    //error  
    static {  
        customerHolder = new HashSet<T>();  
    }  

不過靜態(tài)的方法的參數(shù)卻是可以被泛化的顺囊,如:

    public static <B extends BusinessType, S extends Serializable> Customer getSpcialCustomer(List<B> types, S serial){  
        return new Customer();  
    }  

這個方法我們就可以通過getTypeParameters()方法來獲取它的參數(shù)化信息肌索,代碼如下:

    Method getSpcialCustomer = SalePolicy.class.getMethod("getSpcialCustomer", new Class[]{List.class,Serializable.class});  
    types = getSpcialCustomer.getTypeParameters();  
    assertEquals("The method declared two TypeVariable.", 2, types.length);  
    assertEquals("One of the TypeVariable is B.", "B", ((TypeVariable)types[0]).getName());  
    assertEquals("And another is S.", "S", ((TypeVariable)types[1]).getName());  

最后,說一下 Annotation 特碳,成員可以通過 isAnnotationPresent(annotationClass) 和 getAnnotation(annotationClass) 方法來判斷是否被某個 Annotation 標(biāo)注诚亚,
不過需要注意的時該annotation自身必須被標(biāo)注為 @Retention(RetentionPolicy.RUNTIME)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末午乓,一起剝皮案震驚了整個濱河市站宗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌益愈,老刑警劉巖梢灭,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡敏释,警方通過查閱死者的電腦和手機(jī)库快,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來颂暇,“玉大人缺谴,你說我怎么就攤上這事《欤” “怎么了湿蛔?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長县爬。 經(jīng)常有香客問我阳啥,道長,這世上最難降的妖魔是什么财喳? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任察迟,我火速辦了婚禮,結(jié)果婚禮上耳高,老公的妹妹穿的比我還像新娘扎瓶。我一直安慰自己,他們只是感情好泌枪,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布概荷。 她就那樣靜靜地躺著,像睡著了一般碌燕。 火紅的嫁衣襯著肌膚如雪误证。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天修壕,我揣著相機(jī)與錄音愈捅,去河邊找鬼。 笑死慈鸠,一個胖子當(dāng)著我的面吹牛蓝谨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播青团,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼像棘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了壶冒?” 一聲冷哼從身側(cè)響起缕题,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胖腾,沒想到半個月后调窍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡围小,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了宵睦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡墅诡,死狀恐怖壳嚎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情末早,我是刑警寧澤烟馅,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站然磷,受9級特大地震影響郑趁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜姿搜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一寡润、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧舅柜,春花似錦梭纹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至知举,卻和暖如春瞬沦,著一層夾襖步出監(jiān)牢的瞬間太伊,已是汗流浹背雇锡。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留僚焦,地道東北人锰提。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像芳悲,于是被迫代替她去往敵國和親立肘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容