什麼是泛型?

1、泛型的概念

泛型是 Java SE5 出現的新特性,泛型的本質是類型參數化或參數化類型,在不建立新的類型的狀況下,經過泛型指定的不一樣類型來控制形參具體限制的類型。html

2、泛型的意義

通常的類和方法,只能使用具體的類型:要麼是基本類型,要麼是自定義的類。若是要編寫能夠應用於多種類型的代碼,這種刻板的限制對代碼的束縛就會很大。java

Java 在引入泛型以前,表示可變對象,一般使用 Object 來實現,可是在進行類型強制轉換時存在安全風險。有了泛型後:編程

  • 編譯期間肯定類型,保證類型安全,放的是什麼,取的也是什麼,不用擔憂拋出 ClassCastException 異常。數組

  • 提高可讀性,從編碼階段就顯式地知道泛型集合、泛型方法等處理的對象類型是什麼。安全

  • 泛型合併了同類型的處理代碼提升代碼的重用率,增長程序的通用靈活性。ide

舉個例子:函數

public static void method1() {
    List list = new ArrayList();
    List.add(22);
    List.add("hncboy");
    List.add(new Object());
​
    for (Object o : list) {
        System.out.println(o.getClass());
    }
}

未使用泛型前,咱們對集合能夠進行任意類型的 add 操做,遍歷結果都被轉換成 Object 類型,由於不肯定集合裏存放的具體類型,輸出結果以下所示。學習

class java.lang.Integer
class java.lang.String
class java.lang.Object

採用泛型以後,建立集合對象能夠明確的指定類型,在編譯期間就肯定了該集合存儲的類型,存儲其餘類型的對象編譯器會報錯。這時遍歷集合就能夠直接採用明確的 String 類型輸出。測試

public static void method2() {
    List<String> list = new ArrayList();
    list.add("22");
    list.add("hncboy");
    //list.add(new Object()); 報錯
for (String s : arrayList) {
        System.out.println(s);
    }
}

3、泛型的表示

 

泛型能夠定義在類、接口、方法中,分別表示爲泛型類、泛型接口、泛型方法。泛型的使用須要先聲明,聲明經過<符號>的方式,符號能夠任意,編譯器經過識別尖括號和尖括號內的字母來解析泛型。泛型的類型只能爲類,不能爲基本數據類型。尖括號的位置也是固定的,只能在類名以後方法返回值以前this

通常泛型有約定的符號:E 表明 Element,<E> 一般在集合中使用;T 表明 Type,<T >一般用於表示類;K 表明 Key,V 表明 Value,<K, V> 一般用於鍵值對的表示;? 表明泛型通配符。

泛型的表達式有以下幾種:

  • 普通符號 <T>

  • 無邊界通配符 <?>

  • 上界通配符 <? extends E> 父類是 E

  • 下界通配符 <? super E> 是 E 的父類

4、泛型的使用

4.1 泛型類

將泛型定義在類名後,使得用戶在使用該類時,根據不一樣狀況傳入不一樣類型。在類上定義的泛型,在實例方法中能夠直接使用,不須要定義,可是靜態方法上的泛型須要在靜態方法上聲明,不能直接使用。舉個例子:

public class Test<T> {
    
    private T data;
​
    public T getData() {
        return data;
    }
​
    /** 這種寫法是錯誤的,提示 T 未定義 */
    /*public static T get() {
        return null;
    }*/
    /** 正確寫法,該方法上的 T 和類上的 T 雖然同樣,可是是兩個指代,能夠徹底相同,互不影響 */
    public static <T> T get() {
        return null;
    }
    
    public void setData(T data) {
        this.data = data;
    }
}

4.2 泛型方法

泛型方法,是在調用方法時指明的具體的泛型類型。雖然類上定義的泛型,實例方法中能夠直接使用,可是該方法不屬於泛型方法。舉個例子:get 方法爲泛型方法,並且該程序能編譯經過運行,由於尖括號裏的每一個元素都指代一種未知類型,能夠爲任何符號,尖括號裏的 String 並不是 java.lang.String 類型,而是做爲泛型標識 <String>,傳入的 first 爲 Integer 類型,因此該 String 標識符也指代 Integer 類型,返回值天然也是 Integer 類型。不過,應該也不會用這種泛型符號定義在實際狀況中。

public class Test {
​
    public static <String, T, Hncboy> String get(String string, Hncboy hncboy) {
        return string;
    }
​
    public static void main(String[] args) {
        Integer first = 666;
        Double second = 888.0;
        Integer result = get(first, second);
        System.out.println(result);
    }
}

4.3 泛型通配符

? 爲泛型非限定通配符,表示類型未知,不用聲明,能夠匹配任意的類。該通配符只能讀,不能寫,且不對返回值進行操做。也能夠將非限定通配符出現的地方用普通泛型標識,不過使用通配符更簡潔。舉個例子:

test1() 是經過通配符來輸出集合的每個元素的,test2() 和 test1() 的做用同樣,只不過將通配符用 <T> 來代替了;test3() 用來演示集合在通配符的狀況下寫操做,發現編譯器報錯,int 和 String 都不屬於 ? 類型,固然放不進集合,由於全部類都有 null 元素,因此能夠放進集合。好比主函數傳的是 List<Double>,而想要在集合裏添加一個 String,這是不可能的;test4() 的寫法也是錯的,? 是不肯定,返回值返回不了;test5() 的用法使用來比較 List<Object> 和 List<?> 的,在主函數裏調用 test5(list) 報錯的,顯示 java: 不兼容的類型: java.util.List<java.lang.Integer>沒法轉換爲java.util.List<java.lang.Object>,由於 List<Integer> 不是 List<Object> 的子類。

public class Test {
​
    public static void test1(List<?> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
​
    public static <T> void test2(List<T> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
​
    public static void test3(List<?> list) {
        //list.add(1); capture of ?
        //list.add("1"); capture of ?
        list.add(null);
    }
​
    /*public static ? test4(List<?> list) {
        return null;
    }*/
    
    public static void test5(List<Object> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
    
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        test1(list);
        test2(list);
        //test5(list);
    }
}

經過使用泛型通配符能夠實現泛型的上下邊界 <? extend T> 和 <? super T>,下面將使用 Number 類以及該類的子類來演示這兩種上下型邊界,Number 類的關係圖以下。

<? extends Number> 表示類型爲 Number 或 Number 的子類,<? super Integer> 表示類型爲 Integer 或 Integer 的父類,舉個例子,method1 方法測試是上邊界 Number,因爲 arrayList1 和 arrayList2 的泛型都爲 Number 或其子類,因此能夠插入成功,而 arrayList3 的類型 String 和 Number 無關,所以編譯報錯。method2 方法測試的是下邊界 Integer,因爲 arrayList4,arrayList5 和 arrayList7 種的類型 Integer、Object 和 Number 都爲 Integer 的父類,因此插入成功,而 arrayList7 的類型 Double,所以插入失敗。

public class Generic {
    
    public static void main(String[] args) {
        ArrayList<Integer> arrayList1 = new ArrayList<>();
        ArrayList<Number> arrayList2 = new ArrayList<>();
        ArrayList<String> arrayList3 = new ArrayList<>();
        method1(arrayList1);
        method1(arrayList2);
        //method1(arrayList3);
        
        ArrayList<Integer> arrayList4 = new ArrayList<>();
        ArrayList<Object> arrayList5 = new ArrayList<>();
        ArrayList<Number> arrayList6 = new ArrayList<>();
        ArrayList<Double> arrayList7 = new ArrayList<>();
        method2(arrayList4);
        method2(arrayList5);
        method2(arrayList6);
        //method2(arrayList7)
    }
    
    public static void method1(ArrayList<? extends Number> arrayList) {
    }
    
    public static void method2(ArrayList<? super Integer> arrayList) {
    }
}

 

4.4 泛型接口

泛型接口就是在接口上定義的泛型,當一個類型未肯定的類實現接口時,須要聲明該類型。舉個例子:

public interface CalcGeneric<T> {
    T add(T num1, T num2);
}
​
public class CalculatorGeneric<T> implements CalcGeneric<T> {
​
    @Override
    public T add(T num1, T num2) {
        return null;
    }
}

4.5 泛型數組

數組是支持協變的,什麼是數組的協變呢?舉個例子:這段代碼中,數組支持以 1 的方式定義數組,由於 Integer 是 Number 的子類,一個 Integer 對象也是一個 Number 對象,因此一個 Integer 的數組也是一個 Number 的數組,這就是數組的協變。雖然這種寫法編譯時能經過,可是數組實際上存儲的是 Integer 對象,若是加入 Double 對象,那麼在運行時就會拋出 ArrayStoreException 異常,該種設計存在缺陷。3 方式所示的定義數組方式編譯錯誤,4 所指示的代碼纔是正確的。泛型是不變的,沒有內建的協變類型,使用泛型的時候,類型信息在編譯期會被類型擦除,因此泛型將這種錯誤檢測移到了編譯器。泛型的設計目的之一就是保證了類型安全,讓這種運行時期的錯誤在編譯期就能發現,因此泛型是不支持協變的,如 5 所示的該行代碼會有編譯錯誤,

public class Test {
​
    public static void main(String[] args) {
        Number[] numbers = new Integer[10]; // 1
        // java.lang.ArrayStoreException: java.lang.Double
        numbers[0] = new Double(1); // 2
        //List<String>[] list = new ArrayList<String>[10]; // 3
        List<String>[] list2 = new ArrayList[10]; // 4
        //List<Number> list3 = new ArrayList<Integer>(); // 5
    }
}

4.6 泛型擦除

在泛型內部,沒法得到任何有關泛型參數類型的信息,泛型只在編譯階段有效,泛型類型在邏輯上可當作是多個不一樣的類型,可是其實質都是同一個類型。由於泛型是在JDK5以後纔出現的,須要處理 JDK5以前的非泛型類庫。擦除的核心動機是它使得泛化的客戶端能夠用非泛化的類庫實現,反之亦然,這常常被稱爲"遷移兼容性"。

代價:泛型不能用於顯式地引用運行時類型地操做之中,例如轉型、instanceof 操做和 new 表達式,由於全部關於參數地類型信息都丟失了。不管什麼時候,當你在編寫這個類的代碼的時候,提醒本身,他只是個Object。catch 語句不能捕獲泛型類型的異常。

舉個例子:這串代碼的運行輸出是,所以可見泛型在運行期間對類型進行了擦除。

class java.util.ArrayList
class java.util.ArrayList
true
public static void method1() {
    List<Integer> integerArrayList = new ArrayList();
    List<String> stringArrayList = new ArrayList();
​
    System.out.println(integerArrayList.getClass());
    System.out.println(stringArrayList.getClass());
    System.out.println(integerArrayList.getClass() == stringArrayList.getClass());
}

將上面的 Java 代碼編譯成字節碼後查看也可看見兩個集合都是 java/util/ArrayList

public static method1()V
    L0
    LINENUMBER 14 L0
    NEW java/util/ArrayList
    DUP
    INVOKESPECIAL java/util/ArrayList.<init> ()V
    ASTORE 0
    L1
    LINENUMBER 15 L1
    NEW java/util/ArrayList
    DUP
    INVOKESPECIAL java/util/ArrayList.<init> ()V
    ASTORE 1

由於在運行期間類型擦除的關係,能夠經過反射在運行期間修改集合能添加的類,不過添加後查詢該集合會拋出 ClassCastException 異常,代碼以下。

public static void method4() throws Exception {
    ArrayList<String> stringArrayList = new ArrayList<>();
    stringArrayList.add("hnc");
    stringArrayList.add("boy");
    System.out.println("以前長度:" + stringArrayList.size());
​
    // 經過反射增長元素
    Class<?> clazz = stringArrayList.getClass();
    Method method = clazz.getDeclaredMethod("add", Object.class);
    method.invoke(stringArrayList, 60);
​
    System.out.println("以後長度:" + stringArrayList.size());
    // 存的仍是 Integer 類型
    // java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    for (int i = 0; i < stringArrayList.size(); i++) {
        System.out.println(stringArrayList.get(i).getClass());
    }
}

5、總結

泛型在平時的學習中用到的仍是挺多的。

  • 數組不支持泛型

  • 泛型的類型不能爲基礎數據類型

  • 泛型只在編譯階段有效

 

Java 編程思想

碼出高效 Java 開發手冊

java 泛型詳解

相關文章
相關標籤/搜索