《轉》ParameterizedType getGenericSuperclass 獲取泛型引數;class getClass區別

加瓦攻城獅發表於2018-07-05

反射的基礎知識

請前往上一篇隨筆 java 反射基礎

那麼,Base.class 與 getClass(),這兩個方法來獲取類的位元組碼的時候,有什麼不一樣的地方呢?當然有不一樣的地方了,Base.class 是寫死了的,它得到的永遠是 Base 類的位元組碼,

而 getClass() 方法則不同,在上面程式碼註釋中的第一、二行註釋我用了“實際執行的類”6個字,這幾個字很重要,一定要理解,如果無法理解,下面的你可能就看不懂了。

為了方便大家的理解,下面插加一個小例子來加以說明 類.class 與 getClass() 兩種方法來獲取類的位元組碼有什麼區別:

package test; /**


  • @描述 超類
  • @作者 fancy
  • @郵箱 fancydeepin@yeah.net
  • @日期 2012-8-25


*/ public class Father {

public Father (){
   
    System.out.println("Father 類的構造子:");
    System.out.println("Father.class :" + Father.class);
    System.out.println("getClass()      :" + getClass());
}
複製程式碼

}

package test;

/**


  • @描述 超類的子類(超類的實現類)
  • @作者 fancy
  • @郵箱 fancydeepin@yeah.net
  • @日期 2012-8-25


*/ public class Children extends Father{

}

package test; /**


  • @描述 測試類
  • @作者 fancy
  • @郵箱 fancydeepin@yeah.net
  • @日期 2012-8-25


*/ public class Test {

public static void main(String[] args){
   
    new Children(); //實際執行的類是Children(Father類的子類或者說是實現類)
}
複製程式碼

}

後臺列印輸出的結果:

Father 類的構造子: Father.class :class test.Father getClass() :class test.Children

從列印出的結果看來,類.class 與 getClass() 的區別很明瞭了,getClass() 獲取的是實際執行的類的位元組碼,它不一定是當前類的 Class,有可能是當前類的子類的 Class,具體是哪

個類的 Class,需要根據實際執行的類來確定,new 哪個類,getClass() 獲取的就是哪個類的 Class,而 類.class 獲取得到的 Class 永遠只能是該類的 Class,這點是有很大的區別的。

這下“實際執行的類”能理解了吧,那麼上面的那段被拆分的程式碼也就不難理解了,getClass() 理解了那 clazz.getGenericSuperclass() 也就沒什麼問題了吧,千萬不要以為

clazz.getGenericSuperclass() 獲取得到的是 Object 類那就成了,實際上假如當前執行的類是 Base 類的子類,那麼 clazz.getGenericSuperclass() 獲取得到的就是 Base 類。

再者就是最後一句,(Class) parameterizedType[0],怎麼就知道第一個引數(parameterizedType[0])就是該泛型的實際型別呢?很簡單,因為 Base 的泛型的型別

引數列表中只有一個引數,所以,第一個元素就是泛型 T 的實際引數型別。

其餘的已經加了註釋,看一下就明白了,這裡不多解釋,下面 Base 這個類是不是就直接能使用了呢?來看一下就知道了:

package test; /**


  • @描述 測試類
  • @作者 fancy
  • @郵箱 fancydeepin@yeah.net
  • @日期 2012-8-25


*/ public class Test {

public static void main(String[] args){
   
    Base<String> base = new Base<String>();
    System.out.println(base.getEntityClass());                        //列印輸出 null
//    System.out.println(base.getEntityName());                //丟擲 NullPointerException 異常
//    System.out.println(base.getEntitySimpleName()); //丟擲 NullPointerException 異常
}
複製程式碼

}

從列印的結果來看,Base 類並不能直接來使用,為什麼會這樣?原因很簡單,由於 Base 類中的 clazz.getGenericSuperclass() 方法,如果隨隨便便的就確定 Base 類的泛型的型別

引數,則很可能無法通過 Base 類中的 if 判斷,導致 entityClass 的值為 null,像這裡的 Base,String 的 超類是 Object,而 Object 並不能通過 if 的判斷語句。

Base 類不能夠直接來使用,而是應該通過其子類來使用,Base 應該用來作為一個基類,我們要用的是它的具體的子類,下面來看下程式碼,它的子類也不是隨便寫的:

package test; /**


  • @描述 Base類的實現類
  • @作者 fancy
  • @郵箱 fancydeepin@yeah.net
  • @日期 2012-8-25


*/ public class Child extends Base{

}

從上面程式碼來看,Base 的泛型型別引數就是 Base 的子類本身,這樣一來,當使用 Base 類的子類 Child 類時,Base 類就能準確的獲取到當前實際執行的類的 Class,來看下怎麼使用

package test; /**


  • @描述 測試類
  • @作者 fancy
  • @郵箱 fancydeepin@yeah.net
  • @日期 2012-8-25


*/ public class Test {

public static void main(String[] args){
   
    Child child = new Child();
    System.out.println(child.getEntityClass());
    System.out.println(child.getEntityName());
    System.out.println(child.getEntitySimpleName());
}
複製程式碼

}

後臺列印輸出的結果:

class test.Child test.Child Child

好了,文章接近尾聲了,如果你能理解透這個例子,你可以將這個思想運用到 DAO 層面上來,以 Base 類作為所有 DAO 實現類的基類,在 Base 類裡面實現資料庫的 CURD 等基本

操作,然後再使所有具體的 DAO 類來實現這個基類,那麼,實現這個基類的所有的具體的 DAO 都不必再實現資料庫的 CURD 等基本操作了,這無疑是一個很棒的做法。

(通過反射獲得泛型的實際型別引數)補充:

泛型反射的關鍵是獲取 ParameterizedType 介面,再呼叫 ParameterizedType 介面中的 getActualTypeArguments() 方法就可獲得實際繫結的型別。

由於去引數化(擦拭法),也只有在 超類(呼叫 getGenericSuperclass 方法) 或者成員變數(呼叫 getGenericType 方法)或者方法(呼叫 getGenericParameterTypes 方法)

像這些有方法返回 ParameterizedType 型別的時候才能反射成功。

上面只談到超類如何反射,下面將變數和方法的兩種反射補上:

通過方法,反射獲得泛型的實際型別引數:

package test;

import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collection;

/**


  • @描述 測試類
  • @作者 fancy
  • @郵箱 fancydeepin@yeah.net
  • @日期 2012-8-26


*/ public class Test {

public static void main(String[] args){
    /**
     * 泛型編譯後會去引數化(擦拭法),因此無法直接用反射獲取泛型的引數型別
     * 可以把泛型用做一個方法的引數型別,方法可以保留引數的相關資訊,這樣就可以用反射先獲取方法的資訊
     * 然後再進一步獲取泛型引數的相關資訊,這樣就得到了泛型的實際引數型別
     */
    try {
        Class<?> clazz = Test.class; //取得 Class
        Method method = clazz.getDeclaredMethod("applyCollection", Collection.class); //取得方法
        Type[] type = method.getGenericParameterTypes(); //取得泛型型別引數集
        ParameterizedType ptype = (ParameterizedType)type[0];//將其轉成引數化型別,因為在方法中泛型是引數,且Number是第一個型別引數
        type = ptype.getActualTypeArguments(); //取得引數的實際型別
        System.out.println(type[0]); //取出第一個元素
    } catch (Exception e) {
        e.printStackTrace();
    }
}

//宣告一個空的方法,並將泛型用做為方法的引數型別
public void applyCollection(Collection<Number> collection){
   
}
複製程式碼

}

後臺列印輸出的結果:

class java.lang.Number

通過欄位變數,反射獲得泛型的實際型別引數:

package test;www.2cto.com

import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collection; import java.util.Map;

/**


  • @描述 測試類
  • @作者 fancy
  • @郵箱 fancydeepin@yeah.net
  • @日期 2012-8-26


*/ public class Test {

private Map<String, Number> collection;

public static void main(String[] args){
   
    try {
       
        Class<?> clazz = Test.class; //取得 Class
        Field field = clazz.getDeclaredField("collection"); //取得欄位變數
        Type type = field.getGenericType(); //取得泛型的型別
        ParameterizedType ptype = (ParameterizedType)type; //轉成引數化型別
        System.out.println(ptype.getActualTypeArguments()[0]); //取出第一個引數的實際型別
        System.out.println(ptype.getActualTypeArguments()[1]); //取出第二個引數的實際型別
       
    } catch (Exception e) {
        e.printStackTrace();
    }
}
複製程式碼

}

後臺列印輸出的結果:

class java.lang.String class java.lang.Number

相關文章