父類通過泛型獲得子類Class類型 以及Type體系


 正文前先來一波福利推薦:

 福利一:

百萬年薪架構師視頻,該視頻可以學到很多東西,是本人花錢買的VIP課程,學習消化了一年,為了支持一下女朋友公眾號也方便大家學習,共享給大家。

福利二:

畢業答辯以及工作上各種答辯,平時積累了不少精品PPT,現在共享給大家,大大小小加起來有幾千套,總有適合你的一款,很多是網上是下載不到。

獲取方式:

微信關注 精品3分鍾 ,id為 jingpin3mins,關注后回復   百萬年薪架構師 ,精品收藏PPT  獲取雲盤鏈接,謝謝大家支持!

-----------------------正文開始---------------------------

 

1、背景介紹

在實現SSH框架中,DAO層向數據庫持久化的過程中,因為大部分保存對象的方法都會調用到sava();所有索性就把save delete update select 方法進行封裝到父類中,這時候就遇到了個問題,子類在調用這些方法的時候,需要根據子類的類型獲知子類Class類型;這個時候可以通過傳入泛型,根據泛型的類型來獲取子類的Class類型; 

 

2、實現代碼范例

父類:public abstract class Parents<E>

{

  private Class<?> child;

 

public Parents() 
    {
         Class<?> c = this.getClass(); //子類創建 會創建父類 子類調用時 此處的this是子類
         Type t = c.getGenericSuperclass(); //獲得帶有泛型的父類
         if (t instanceof ParameterizedType) 
         {
              Type[] p = ((ParameterizedType) t).getActualTypeArguments();  //取得所有泛型
              this.child= (Class<E>) p[0];
         }

}

  在子類調用父類的方法時,如果父類的方法中需要知道具體子類的Class類型 則可以直接使用Child來使用;

  此處的原理就是在子類繼承父類的時候 帶有泛型 然后子類在創建的時候,會調用父類的構造函數,構造函數中存在this指的的是子類,然后通過獲得父類,再獲得父類的泛型

;通過泛型找到子類類型;

3、原理分析

  該實現是通過反射技術實現;下面看具體的分析;

3、1 ParameterizedType 類

ParameterizedType,參數化類型,形如:Object<T, K>,即常說的泛型,是Type的子接口。

public interface ParameterizedType extends Type {
    //1.獲得<>中實際類型
    Type[] getActualTypeArguments();
    //2.獲得<>前面實際類型
    Type getRawType();
    //3.如果這個類型是某個類型所屬,獲得這個所有者類型,否則返回null
    Type getOwnerType();
}

1.getActualTypeArguments 
獲得參數化類型中<>里的類型參數的類型,因為可能有多個類型參數,例如Map<K, V>,所以返回的是一個Type[]數組。 
注意:無論<>中有幾層<>嵌套,這個方法僅僅脫去最外層的<>,之后剩下的內容就作為這個方法的返回值,所以其返回值類型不一定。 
例如: 

1. List<ArrayList> a1;//這里返回的是,ArrayList,Class類型 
2. List<ArrayList<String>> a2;//這里返回的是ArrayList<String>,ParameterizedType類型  可以繼續通過調用getActualTypeArguments獲得其泛型類型
3. List<T> a3;//返回的是T,TypeVariable類型 
4. List<? extends Number> a4; //返回的是WildcardType類型 
5. List<ArrayList<String>[]> a5;//GenericArrayType 
要注意,ArrayList與ArrayList<String>的不同。
public static void main(String[] args) throws Exception
    {
        Method method = new Main().getClass().getMethod("test", List.class);//這里的第二個參數,和getRawType()意義類似
        Type[] types = method.getGenericParameterTypes();
        ParameterizedType pType = (ParameterizedType) types[0];
        Type type = pType.getActualTypeArguments()[0];
        System.out.println(type);
        //type是Type類型,但直接輸出的不是具體Type的五種子類型,而是這五種子類型以及WildcardType具體表現形式
        System.out.println(type.getClass().getName());
    }
    public void test(List<ArrayList<String>[]> a)
   { }

2.getRawType 
返回最外層<>前面那個類型,即Map<K ,V>的Map

Map<Integer, String> maps = new HashMap<>();
ParameterizedType pType = (ParameterizedType) maps.getClass().getGenericSuperclass();//獲得HashMap的父類
System.out.println(pType.getRawType());//class java.util.AbstractMap
if(pType.getRawType() instanceof Class){
      System.out.println("true");//true
}
//注意類型(Type)與類(Class)的區別

3、2 Type類 

  Type是java類型信息體系中的頂級接口,其中Class就是Type的一個直接實現類。此外,Type還有有四個直接子接口:ParameterizedType,TypeVariable,WildcardType,GenericArrayType。

        引用這位仁兄對這幾個接口的介紹[轉載]:

      Type
      它是所有類型的公共接口。包括原始類型、參數化類型、數組類型、類型變量和基本類型。ParameterizedType, TypeVariable, WildcardType,GenericArrayType這四個接口都是它的子接口。   

3、2、1 GenericDeclaration    

  這個接口Class、Method、Constructor都有實現,我們就是要用這個接口的getTypeParameters方法,它返回一個TypeVariable[]數組,這個數組里面就是我們定義的類型變量T和K,順序與我們聲明時一樣。如果用循環語句將數組打印出來,你會發現只會輸出T和K,這可不是我們想要的結果,那么想要獲得預期的結果怎么辦呢?請繼續往下看。
    

3、2、2 TypeVariable

  它表示類型變量。比如T,比如extends Comparable<? super T> & Serializable,這個接口里面有個getBounds()方法,它用來獲得類型變量上限的Type數組,如果沒有定義上限,則默認設定上限為Object,請注意TypeVariable是接口,實際得到的是TypeVariableImpl實現類,下面幾個接口都一樣。
    
拿T和K來說明,T沒有定義任何上限,所以它就有一個默認上限java.lang.Object,實際跟蹤代碼的時候你會發現T的bounds屬性為空,只有在調用了getBounds()方法后,才會有一個Type[1]數組[class java.lang.Object]。而對於K來說,調用了getBounds方法后,得到的數組是[java.lang.Comparable<? super T>, interface java.io.Serializable],它們的類型卻是不一樣的,第1個是ParameterizedType,而第二個是Class
    

3、2、3 ParameterizedType

  ParameterizedType表示參數化類型,就是上面說的java.lang.Comparable<? super T>,再比如List<T>,List<String>,這些都叫參數化類型。得到Comparable<? super T>之后,再調用getRawType()與getActualTypeArguments()兩個方法,就可以得到聲明此參數化類型的類(java.lang.Comparable)和實際的類型參數數組([? super T]),而這個? super T又是一個WildcardType類型。

3、2、4 WildcardType

    它用來描述通配符表達式,上面返回的? super T正好是這個類型。然后調用getUpperBounds()上限和getLowerBounds()下限這兩個方法,獲得類型變量?的限定類型(上下限),對於本例的通配符(?),它的上限為java.lang.Object,下限為T
通過上面幾個接口的分析,可以將Person類的泛型參數都解析出來,那么Person的超類以及實現的接口該怎么處理呢?Class類里面同樣在1.5版本加入了getGenericSuperclass()與
getGenericInterfaces()兩個方法,用於返回帶參數化類型的超類與接口。

3、2、5 GenericArrayType其實就是泛型數組類型。

   

  我們說Class在一定程度上挽救了擦除的類型信息,我們就可以通過這幾個接口來獲取被擦除的類型參數信息,這幾個接口無非就是對類型參數的一個分類,通過它們提供的一些方法,我們可以逐步的獲取到最原始的類型參數的Class對象。

  具體的說明和API大家可以去看文檔,我這里記錄一個實際的應用,當然在各種框架中的應用比比皆是。

  在JavaEE的Dao層我們一般都會封裝出一個通用的泛型BaseDao,它可以實現對各種實體例如User,Order的基本CRUD,然后具體的UserDao,OrderDao等等會去繼承它,提供其他的Dao方法:

public class UserDao extends BaseDao<User>{}

我使用的BaseDao是基於DBUtils的,它需要實體的Class對象才能進行通用的查詢方法,例如User的Class對象,我們可以通過構造函數,函數參數等手段傳遞給BaseDao,但是有了反射,可以有更優雅的實現。
public class BaseDao<T> {
    
    private Class<T> clszz;
    
    public BaseDao(){
        Type type = this.getClass().getGenericSuperclass();//拿到帶類型參數的泛型父類
        if(type instanceof ParameterizedType){//這個Type對象根據泛型聲明,就有可能是4中接口之一,如果它是BaseDao<User>這種形式
            ParameterizedType parameterizedType = (ParameterizedType) type;
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();//獲取泛型的類型參數數組
            if(actualTypeArguments != null && actualTypeArguments.length == 1){
                if(actualTypeArguments[0] instanceof Class){//類型參數也有可能不是Class類型
                    this.clszz = (Class<T>) actualTypeArguments[0];
                }else{
                    //例如: BaseDao<BaseDao<User>>,獲取到的就不是Class,而又是ParameterizedType,即嵌套的
                    ParameterizedType,一層一層剝開,最終是可以得到User的Class對象的
                }
            }
        }
    }
    
    public T get(String sql,Object...params){
        QueryRunner qr = new QueryRunner();
        T obj;
        Connection connection;
        try {
            connection = JdbcUtil.getConnection();
            obj = qr.query(connection,sql,new BeanHandler<T>(clszz),params);
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
        return obj;
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM