1. 程式人生 > >java泛型詳解和反射泛型通用BaseDao實現

java泛型詳解和反射泛型通用BaseDao實現

一 泛型的基本概念
1.1什麼是泛型?
泛型,即“引數化型別”。一提到引數,最熟悉的就是定義方法時有形參,然後呼叫此方法時傳遞實參。那麼引數化型別怎麼理解呢?顧名思義,就是將型別由原來的具體的型別引數化,類似於方法中的變數引數,此時型別也定義成引數形式(可以稱之為型別形參),然後在使用/呼叫時傳入具體的型別(型別實參)。

1.2泛型的作用?
JDK5中的泛型允許程式設計師在編寫集合程式碼時,就限制集合的處理型別,從而把原來程式執行時可能發生問題,轉變為編譯時的問題,以此提高程式的可讀性和穩定性(尤其在大型程式中更為突出)。

1.3需要掌握的知識:
基本用法、泛型擦除、泛型類/泛型方法/泛型介面、泛型關鍵字、反射泛型(案例)!
二 基本用法


使用泛型的好處就是可以在編譯時幫我們檢查錯誤。
從例子入手:

// 執行時期異常 
    @Test
    public void testGeneric() throws Exception {
        // 集合的宣告
        List list = new ArrayList();
        list.add("test");
        list.add(1);        
        // 集合的使用
        String str = (String) list.get(1);      
    }
上面程式碼,程式在執行時期會出現異常:java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

原因就是,ArrayList可以存放任意型別,預設為Object型別,例子中添加了一個String型別,添加了一個Integer型別,再使用時以String的方式使用第二個元素,因此程式崩潰了。為了解決類似這樣的問題(在編譯階段就可以解決),泛型應運而生。

// 使用泛型
    @Test
    public void testGeneric2() throws Exception {
        // 宣告泛型集合的時候指定元素的型別
        List<String> list = new ArrayList<String>();
        list
.add("test"); // list.add(1);// 編譯時期直接報錯 String str = list.get(1); }

注意:泛型的型別引數只能是類型別(包括自定義類),不能是簡單型別(如int)

三 泛型擦除
泛型是提供給javac編譯器使用的,它用於限定集合的輸入型別,讓編譯器在原始碼級別上,即擋住向集合中插入非法資料。但編譯器編譯完帶有泛形的java程式後,生成的class檔案中將不再帶有泛形資訊,以此使程式執行效率不受到影響,這個過程稱之為“擦除”。
例如1:

List<String> stringArrayList = new ArrayList<String>(); 
List<Integer> integerArrayList = new ArrayList<Integer>(); 
Class classStringArrayList = stringArrayList.getClass();
Class class IntegerArrayList = integerArrayList.getClass(); if(classStringArrayList.equals(classIntegerArrayList)){
     Log.d("泛型測試","型別相同"); 
}

輸出結果:D/泛型測試: 型別相同。
例子2:

// 泛型擦除例項 

    public void save(List<Person> p){
    }
    public void save(List<Dept> d){    // 報錯: 與上面方法編譯後一樣
    }

在例子2中兩個重名的方法看似是方法的過載,其實引數型別一致,會報錯。因為在編譯過程中,正確檢驗泛型結果後,會將泛型的相關資訊擦出,並且在物件進入和離開方法的邊界處新增型別檢查和型別轉換的方法。也就是說,泛型資訊不會進入到執行時階段。
總結就是,泛型型別在邏輯上看似是多個不同的型別,實際上都是相同的基本型別。

四 泛型的使用
4.1 泛型方法

public class GenericDemo {

    // 定義泛型方法
    public <T> T save(T t) {
        return null;
    }

    // 測試方法
    @Test
    public void testMethod() throws Exception {
        // 使用泛型方法:  在使用泛型方法的時候,確定泛型型別
        save(1);
    }
}

泛型方法的泛型宣告必須是在修飾符之前返回值型別之後定義。例如上面的 “public <> T”;
只有聲明瞭”<>”的方法才是泛型方法,泛型類中的使用了泛型的成員方法並不是泛型方法。
還有一點要注意的是:泛型方法在使用的時候,才確定泛型型別。如上面程式碼save(1);被呼叫的的時候才確定型別為Integer.

泛型方法中的T可以隨便寫為任意標識,常見的如T、E、K、V等形式的引數常用於表示泛型。並且泛型的數量也可以為任意多個。
例如:

public class GenericDemo {

    // 定義泛型方法
    public <K,T> T save(T t,K k) {
        return null;
    }

    // 測試方法
    @Test
    public void testMethod() throws Exception {
        // 使用泛型方法:  在使用泛型方法的時候,確定泛型型別
        save(1.0f, 1);//Double,Integer型別
    }
}

4.2 泛型類的使用

public class GenericDemo<T> {

    // 定義泛型方法
    public <K> T save(T t,K k) {
        return null;
    }

    public void update(T t) {

    }

    // 測試方法
    @Test
    public void testMethod() throws Exception {

        // 泛型類:  在建立泛型類物件的時候,確定型別
        GenericDemo<String> demo = new GenericDemo<String>();
        demo.save("test", 1);
    }
}

從上面例子中我們可以看到 泛型類的型別宣告是在類名之後的。並且在方法中如果需要用到這個T型別可以不用再次宣告,直接可以用了。但是K型別還需要在方法中宣告一下。
還有就是 當建立泛型類物件的例項時,指定型別為String,才確定型別。
4.3 泛型介面
泛型介面與泛型類的定義及使用基本相同。
例如:

//定義一個泛型介面 
public interface Generator<T> { 
    public T next();
 }
//當實現泛型介面的類,未傳入泛型實參時:
/** * 未傳入泛型實參時,與泛型類的定義相同,
在宣告類的時候,需將泛型的宣告也一起加到類中 *
 即 class FruitGenerator<T> implements Generator<T>{ 
* 如果不宣告泛型,如:class FruitGenerator implements Generator<T>,編譯器會報錯:"Unknown class" */ 
如下正確:
class FruitGenerator<T> implements Generator<T>{ 
@Override 
public T next() { 
    return null;
  }
 }

當實現泛型介面的類,傳入泛型實參時:

public class FruitGenerator implements Generator<String> {
@Override
 public String next() {
 return "fruit"; 
} }

注意 當實現類實現泛型介面時,如已將泛型型別傳入實參型別,則所有使用泛型的地方都要替換成傳入的實參型別 即:Generator<>,public T next();中的的T都要替換成傳入的String型別。

五 泛型關鍵字
泛型中:
? 指定只是接收值
extends 元素的型別必須繼承自指定的類
super 元素的型別必須是指定的類的父類
5.1 關鍵字:? 【萬用字元】
當我們在呼叫方法執行之後返回值的時候,或者在方法傳遞引數的時候,我們不清楚到底返回什麼型別,或者傳遞什麼型別,這個時候可以用 ?來代替泛型型別,可以實現擴充套件性。

public class Test {
    // 只帶泛型特徵的方法
    public void save(List<?> list) {
        // 只能獲取、迭代list; 不能修改list
    }

    public static void main(String[] args) {
        // ? 可以接收任何泛型集合, 但是不能修改集合元素; 所以一般在方法引數中用
        List<?> list1 = new ArrayList<String>();
        List<?> list2 = new ArrayList<Integer>();
        Test test = new Test();
        test.save(list1);
        test.save(list2);
        // list.add("");// 會報錯 
    }
}

上面程式碼 List 可以接受兩種泛型型別的集合。但是不能修改它。還有就是上面不用再寫兩個方法來接受不同泛型集合的傳遞引數,而是隻用一個方法來接受所有泛型型別的集合引數。
還有一點就是,之前看到的,型別萬用字元一般是使用?代替具體的型別實參,注意了,此處’?’是型別實參,而不是型別形參 。再直白點的意思就是,此處的?和Number、String、Integer一樣都是一種實際的型別,可以把?看成所有型別的父類。是一種真實的型別。

5.2 關鍵字 : extends 【上限】
extends關鍵字的意思就是型別實參只准傳入某種型別的子類包括自身。也就是會限定範圍。如果不是該父類型別的子類,則報錯。
例如:

public class App_extends {


    /**
     * list集合只能處理 Double/Float/Integer等型別
     * 限定元素範圍:元素的型別要繼承自Number類  (上限)
     * @param list
     */
    public void save(List<? extends Number> list) {
    }

    @Test
    public void testGeneric() throws Exception {
        List<Double> list_1 = new ArrayList<Double>();
        List<Float> list_2 = new ArrayList<Float>();
        List<Integer> list_3 = new ArrayList<Integer>();

        List<String> list_4 = new ArrayList<String>();

        // 呼叫
        save(list_1);//通過
        save(list_2);//通過
        save(list_3);//通過
        //save(list_4);//會報錯 因為String不是Number的子類
    }
}

5.3關鍵字 : super 【下限】
super關鍵字的意思就是型別實參只准傳入某種型別的父類包括自身。也就是所說的上限。
例如:

public class App_super {


    /**
     * super限定元素範圍:必須是String父類   【下限】
     * @param list
     */
    public void save(List<? super String> list) {
    }

    @Test
    public void testGeneric() throws Exception {
        // 呼叫上面方法,必須傳入String的父類
        List<Object> list1 = new ArrayList<Object>();
        List<String> list2 = new ArrayList<String>();

        List<Integer> list3 = new ArrayList<Integer>();
        //save(list1);//通過
        //save(list2);//通過
        //save(list3);//報錯 因為Integer 不是String的父類
    }
}

六 反射泛型(BaseDao案例)
在我們做專案的時候會碰到要實現一個模組比如:管理員模組,個人賬戶模組。我們需要寫AdminDao類、然後在類中寫一大推資料庫操作的方法,增刪改查等。然後我們開始寫AccountDao類,也會再寫一遍增刪改查操作。這個時候我們就想能不能優化一下程式碼,寫一個通用的BaseDao,裡面有通用的增刪改查等常用資料庫操作方法,然後讓其他模組繼承此類,這樣不用每次去寫一遍了,提高效率。
要想寫一個通用的資料庫操作方法,必須知道資料庫中的表名和操作的物件型別。
因此,要想寫一個通用類,我們必須約定資料庫中的表名必須和類名一直。
那麼類名在哪裡可以獲得,只能通過泛型類來實現,然後讓子類繼承BaseDao,在子類中指定父類的泛型型別。
例如:

Class BaseDao<T>{
//根據主鍵查詢通用方法
Public T findById(int id){
//獲取物件
//獲取表
   }
}
Class AdminDao extends BaseDao<Admin>{}

因此在建立子類AdminDao的例項時,將Admin通過泛型傳遞給BaseDao,然後幫助我們實現資料庫通用操作。那麼在父類中如何拿到子類的型別。首先我們要知道
什麼是引數化型別?例如:“ArrayList《String》 ” 為引數化型別
還需要知道一個類。 ParameterizedType 通過這個類 我們能獲取子類的型別。

下面來看一下具體實現。

public class AdminDao extends BaseDao<Admin> {}
public class AccountDao extends BaseDao<Account> {}


/**
 * 所有dao的通用方法
 *
 */
public class BaseDao<T>{

    // 儲存當前執行類的引數化型別中的實際的型別
    private Class clazz;
    // 表名
    private String tableName;



    // 建構函式: 1. 獲取當前執行類的引數化型別; 
    //2. 獲取引數化型別中實際型別的定義(class)
    public BaseDao(){
        //  this  表示當前執行類  (AccountDao/AdminDao)
        //  this.getClass()  當前執行類的位元組碼             (AccountDao.class/AdminDao.class)
        //  this.getClass().getGenericSuperclass();  當前執行類的父類,即為BaseDao<Account>
        //                                           其實就是“引數化型別”, ParameterizedType   
        Type type = this.getClass().getGenericSuperclass();
        // 強制轉換為“引數化型別”  【BaseDao<Account>】
        ParameterizedType pt = (ParameterizedType) type;
        // 獲取引數化型別中,實際型別的定義  【new Type[]{Account.class}】
        Type types[] =  pt.getActualTypeArguments();
        // 獲取資料的第一個元素:Accout.class
        clazz = (Class) types[0];
        // 表名  (與類名一樣,只要獲取類名就可以)
        tableName = clazz.getSimpleName();
    }


    /**
     * 主鍵查詢
     * @param id    主鍵值
     * @return      返回封裝後的物件
     */
    public T findById(int id){
        /*
         * 1. 知道封裝的物件的型別
         * 2. 表名【表名與物件名稱一樣, 且主鍵都為id】
         * 
         * 即,
         *    ---》得到當前執行類繼承的父類  BaseDao<Account>
         *   ----》 得到Account.class
         */

        String sql = "select * from " + tableName + " where id=? ";
        try {
            return JdbcUtils.getQuerrRunner().query(sql, new BeanHandler<T>(clazz), id);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 查詢全部
     * @return
     */
    public List<T> getAll(){
        String sql = "select * from " + tableName ;
        try {
            return JdbcUtils.getQuerrRunner().query(sql, new BeanListHandler<T>(clazz));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}