天天看點

Java泛型深入了解

泛型的優點:

    泛型的主要優點就是讓編譯器保留參數的類型資訊,執行類型檢查,執行類型轉換(casting)操作,編譯器保證了這些類型轉換(casting)的絕對無誤。

        /******* 不使用泛型類型 *******/

        List list1 = new ArrayList();

        list1.add(8080);                                  //編譯器不檢查值

        String str1 = (String)list1.get(0); //需手動強制轉換,如轉換類型與原資料類型不一緻将抛出ClassCastException異常

        /******* 使用泛型類型 *******/

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

        list2.add("value");                 //[類型安全的寫入資料] 編譯器檢查該值,該值必須是String類型才幹通過編譯

        String str2 = list2.get(0); //[類型安全的讀取資料] 不須要手動轉換

泛型的類型擦除:

    Java 中的泛型僅僅存在于編譯期。在将 Java 源檔案編譯完畢 Java 位元組代碼中是不包括泛型中的類型資訊的。使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。

    這個過程就稱為類型擦除(type erasure)。

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

        List<Integer> list2 = new ArrayList<Integer>();

        System.out.println(list1.getClass() == list2.getClass()); // 輸出結果: true

        System.out.println(list1.getClass().getName()); // 輸出結果: java.util.ArrayList

        System.out.println(list2.getClass().getName()); // 輸出結果: java.util.ArrayList

在以上代碼中定義的 List<String> 和 List<Integer> 等類型。在編譯之後都會變成 List。而由泛型附加的類型資訊對 JVM 來說是不可見的。是以第一條列印語句輸出 true,

第二、第三條列印語句都輸出 java.util.ArrayList,這都說明 List<String> 和 List<Integer> 的對象使用的都是同一份位元組碼。執行期間并不存在泛型。

來看一個簡單的樣例:

package test;

import java.util.List;

/**

 * -----------------------------------------

 * @描寫叙述  類型擦除

 * @作者  fancy

 * @郵箱  [email protected]

 * @日期  2012-8-25 <p>

 */

public class GenericsApp {

    public void method(List<String> list){

    }

    /*

     * 編譯出錯,這兩個方法不屬于重載,因為類型的擦除,使得這兩個方法的參數清單的參數均為List類型,

     * 這就相當于同一個方法被聲明了兩次,編譯自然無法通過了

     *

    public void method(List<Integer> list){

    */

}

以此類為例,在 cmd 中 編譯 GenericsApp.java 得到位元組碼(泛型已經擦除)。然後再反編譯這份位元組碼來看下源代碼中泛型是不是真的被擦除了:

從圖中能夠看出,經反編譯後的源代碼中 method 方法的參數變成了 List 類型。說明泛型的類型是真的被擦除了。位元組碼檔案裡不存在泛型。也就是說。執行期間泛型并不存在,它在

編譯完畢之後就已經被擦除了。

泛型類型的子類型:

    泛型類型跟其是否是泛型類型的子類型沒有不論什麼關系。

        List<Object> list1;

        List<String> list2;

        list1 = list2; // 編譯出錯

        list2 = list1; // 編譯出錯

大家都知道,在 Java 中。Object 類是全部類的超類,自然而然的 Object 類是 String 類的超類,按理。将一個 String 類型的對象指派給一個 Object 類型的對象是可行的。

可是泛型中并不存在這種邏輯,用更通俗的話說,泛型類型跟其是否子類型沒有不論什麼關系。

泛型中的通配符(?):

    因為泛型類型與其子類型存在不相關性,那麼在不能确定泛型類型的時候。能夠使用通配符(?

)。通配符(?)能比對随意類型。

        List<?> list;

        List<Object> list1 = null;

        List<String>  list2 = null;

        list = list1;

        list = list2;

限定通配符的上界:

        ArrayList<?

extends Number> collection = null;

        collection = new ArrayList<Number>();

        collection = new ArrayList<Short>();

        collection = new ArrayList<Integer>();

        collection = new ArrayList<Long>();

        collection = new ArrayList<Float>();

        collection = new ArrayList<Double>();

 ? extends XX,XX 類是用來限定通配符的上界,XX 類是能比對的最頂層的類。它僅僅能比對 XX 類以及 XX 類的子類。在以上代碼中,Number 類的實作類有:

AtomicInteger、AtomicLong、 BigDecimal、 BigInteger、 Byte、 Double、 Float、 Integer、 Long、 Short ,是以以上代碼均無錯誤。

限定通配符的下界:

super Integer> collection = null;

        collection = new ArrayList<Object>();

 ? super XX,XX 類是用來限定通配符的下界。XX 類是能比對的最底層的類,它僅僅能比對 XX 類以及 XX 類的超類,在以上代碼中,Integer 類的超類有:

Number、Object,是以以上代碼均能通過編譯無誤。

通過反射獲得泛型的實際類型參數:

    這個就有點難度了。上面已經說到,泛型的類型參數會在編譯完畢以後被擦除,那在執行期間還怎麼來獲得泛型的實際類型參數呢?這個是有點難度了吧?似乎不可能實作的樣子。

    事實上不然。java.lang.Class 類從 Java 1.5 起(假設沒記錯的話),提供了一個 getGenericSuperclass() 方法來擷取直接超類的泛型類型,這就使得擷取泛型的實際類型參數成為

    了可能,以下來看一段代碼。這段代碼非常精辟,非常實用,大家一定要學到手哈:

import java.lang.reflect.ParameterizedType;

 * @描寫叙述  泛型的實際類型參數

public class Base<T> {

    private Class<T> entityClass;

    //代碼塊,也可将其放置到構造子中

    {

        entityClass =(Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];

    //泛型的實際類型參數的類全名

    public String getEntityName(){

        return entityClass.getName();

    //泛型的實際類型參數的類名

    public String getEntitySimpleName(){

        return entityClass.getSimpleName();

    //泛型的實際類型參數的Class

    public Class<T> getEntityClass() {

        return entityClass;

以上代碼的精華全在這句:(Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];

實際上。這句話咋看起來非常難看的明确,了解起來就更加的吃力了,以下容我來将這句複雜的代碼拆分開來,了解起來可能會好些:

        //entityClass =(Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];

        try {

            Class<?> clazz = getClass(); //擷取實際執行的類的 Class

            Type type = clazz.getGenericSuperclass(); //擷取實際執行的類的直接超類的泛型類型

            if(type instanceof ParameterizedType){ //假設該泛型類型是參數化類型

                Type[] parameterizedType = ((ParameterizedType)type).getActualTypeArguments();//擷取泛型類型的實際類型參數集

                entityClass = (Class<T>) parameterizedType[0]; //取出第一個(下标為0)參數的值

            }

        } catch (Exception e) {

            e.printStackTrace();

        }

注意,擷取 Class 執行個體的時候是用 getClass(),而不是用 Base.class,擷取 Class 的方式有三種。這是當中的兩種。另一種是 Class.forName("類全名")。如需了解反射的基礎知識

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

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

而 getClass() 方法則不同,在上面代碼凝視中的第一、二行凝視我用了“實際執行的類”6個字,這幾個字非常重要,一定要了解,假設無法了解。以下的你可能就看不懂了。

為了友善大家的了解。以下插加一個小樣例來加以說明 類.class 與 getClass() 兩種方法來擷取類的位元組碼有什麼差别:

 * @描寫叙述  超類

public class Father {

    public Father (){

        System.out.println("Father 類的構造子:");

        System.out.println("Father.class :" + Father.class);

        System.out.println("getClass()      :" + getClass());

 * @描寫叙述  超類的子類(超類的實作類)

public class Children extends Father{

 * @描寫叙述  測試類

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<T>) parameterizedType[0],怎麼就知道第一個參數(parameterizedType[0])就是該泛型的實際類型呢?非常easy。因為 Base<T> 的泛型的類型

參數清單中僅僅有一個參數。是以,第一個元素就是泛型 T 的實際參數類型。

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

        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 類并不能直接來使用,為什麼會這樣?原因非常easy。因為 Base 類中的 clazz.getGenericSuperclass() 方法,假設随随便便的就确定 Base 類的泛型的類型

參數,則非常可能無法通過 Base 類中的 if 推斷,導緻 entityClass 的值為 null,像這裡的 Base<String>,String 的 超類是 Object,而 Object 并不能通過 if 的推斷語句。

Base 類不能夠直接來使用,而是應該通過其子類來使用,Base 應該用來作為一個基類,我們要用的是它的詳細的子類,以下來看下代碼,它的子類也不是随便寫的:

 * @描寫叙述  Base類的實作類

public class Child extends Base<Child>{

從上面代碼來看,Base 的泛型類型參數就是 Base 的子類本身,這樣一來,當使用 Base 類的子類 Child 類時,Base 類就能準确的擷取到目前實際執行的類的 Class。來看下怎麼使用

 * @郵箱  ​​[email protected]​​

        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 類型的時候才幹反射成功。

上面僅僅談到超類怎樣反射,以下将變量和方法的兩種反射補上:

通過方法。反射獲得泛型的實際類型參數:

import java.lang.reflect.Method;

import java.lang.reflect.Type;

import java.util.Collection;

 * @日期  2012-8-26 <p>

        /**

         * 泛型編譯後會去參數化(擦拭法),是以無法直接用反射擷取泛型的參數類型

         * 能夠把泛型用做一個方法的參數類型。方法能夠保留參數的相關資訊。這樣就能夠用反射先擷取方法的資訊

         * 然後再進一步擷取泛型參數的相關資訊,這樣就得到了泛型的實際參數類型

         */

            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]); //取出第一個元素

    //聲明一個空的方法,并将泛型用做為方法的參數類型

    public void applyCollection(Collection<Number> collection){

class java.lang.Number

通過字段變量,反射獲得泛型的實際類型參數:

package test;www.2cto.com

import java.lang.reflect.Field;

import java.util.Map;

    private Map<String, Number> collection;

            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]); //取出第二個參數的實際類型

class java.lang.String