細說 Java 泛型及其應用

引出泛型

咱們經過以下的示例,引出爲何泛型的概念。java

public class Test {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("abc");
        list.add(2);

        for (int i = 0; i < list.size(); i++) {
            String name = (String) list.get(i); // error
            System.out.println("name:" + name);
        }
    }
}
複製代碼

當獲取列表中的第二個元素時,會報錯,java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String。這是常見的類型轉換錯誤。程序員

當咱們將元素放入到列表中,並無使用指定的類型,在取出元素時使用的是默認的 Object 類型。所以很容易出現類型轉換的異常。數組

咱們想要實現的結果是,集合可以記住集合內元素各種型,且可以達到只要編譯時不出現問題,運行時就不會出現 java.lang.ClassCastException 異常。泛型恰好能知足咱們的需求。安全

什麼是泛型?

泛型,即參數化類型。一提到參數,最熟悉的就是定義方法時有形參,而後調用此方法時傳遞實參。那麼參數化類型怎麼理解呢?顧名思義,就是將類型由原來的具體的類型參數化,相似於方法中的變量參數,此時類型也定義成參數形式(能夠稱之爲類型形參),而後在使用/調用時傳入具體的類型(類型實參)。bash

泛型的本質是爲了參數化類型,即在不建立新的類型的狀況下,經過泛型指定的不一樣類型來控制形參具體限制的類型。在泛型使用過程當中,操做的數據類型被指定爲一個參數,這種參數類型能夠用在類、接口和方法中,分別被稱爲泛型類、泛型接口和泛型方法。微信

泛型的特色

Java 語言中引入泛型是一個較大的功能加強。不只語言、類型系統和編譯器有了較大的變化,已支持泛型,並且類庫也進行了大翻修,因此許多重要的類,好比集合框架,都已經成爲泛型化的了。這帶來了不少好處:app

  1. 類型安全。 泛型的主要目標是提升 Java 程序的類型安全。經過知道使用泛型定義的變量的類型限制,編譯器能夠在一個高得多的程度上驗證類型假設。
  2. 消除強制類型轉換。 泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,而且減小了出錯機會。
  3. 潛在的性能收益。 泛型爲較大的優化帶來可能。在泛型的初始實現中,編譯器將強制類型轉換(沒有泛型的話,程序員會指定這些強制類型轉換)插入生成的字節碼中。

命名類型參數

推薦的命名約定是使用大寫的單個字母名稱做爲類型參數。對於常見的泛型模式,推薦的名稱是:框架

  • K:鍵,好比映射的鍵
  • V:值,好比 List 和 Set 的內容,或者 Map 中的值
  • E:元素
  • T:泛型
public class Generic<T> { 
    //key的類型爲T 
    private T key;

    public Generic(T key) { 
    	//泛型構造方法形參key的類型也爲T
        this.key = key;
    }

    public T getKey() { 
    	//泛型方法getKey的返回值類型爲T
       return key;
    }
}
複製代碼

如上定義了一個普通的泛型類,成員變量的類型爲 T,T的類型由外部指定。泛型方法和泛型構造函數一樣如此。函數

Generic<Integer> genericInteger = new Generic<Integer>(123456); //1

Generic<String> genericString = new Generic<String>("key_vlaue"); // 2

System.out.println("key is " + genericInteger.getKey());
System.out.println("key is " + genericString.getKey());
複製代碼

泛型的類型參數只能是類類型(包括自定義類),不能是簡單類型。傳入的實參類型需與泛型的類型參數類型相同,即爲Integer/String。性能

如上所述,定義的泛型類,就必定要傳入泛型類型實參麼?

並非這樣,在使用泛型的時候若是傳入泛型實參,則會根據傳入的泛型實參作相應的限制,此時泛型纔會起到本應起到的限制做用。若是不傳入泛型類型實參的話,在泛型類中使用泛型的方法或成員變量定義的類型能夠爲任何的類型。

Generic genericString = new Generic("111111");
Generic genericInteger = new Generic(4444);

System.out.println("key is " + genericString.getKey());
System.out.println("key is " + genericInteger.getKey());
複製代碼

如上的代碼片斷,將會輸出以下的結果:

key is 111111
key is 4444
複製代碼

在不傳入泛型類型實參的狀況下,泛型類中使用的泛型防範或成員變量能夠爲 Integer 或 String 等等其餘任意類型。不過須要注意的是,泛型的類型參數只能是類類型,不能是簡單類型。且不能對確切的泛型類型使用 instanceof 操做。對於不一樣傳入的類型實參,生成的相應對象實例的類型是否是同樣的呢?具體看以下的示例:

public class GenericTest {

    public static void main(String[] args) {

        Generic<Integer> name = new Box<String>("111111");
        Generic<String> age = new Box<Integer>(712);

        System.out.println("name class:" + name.getClass());  
        System.out.println("age class:" + age.getClass()); 
        System.out.println(name.getClass() == age.getClass());    // true
    }

}
複製代碼

由輸出結構可知,在使用泛型類時,雖然傳入了不一樣的泛型實參,但並無真正意義上生成不一樣的類型,傳入不一樣泛型實參的泛型類在內存上只有一個,即仍是原來的最基本的類型(本例中爲 Generic),固然在邏輯上咱們能夠理解成多個不一樣的泛型類型。

究其緣由,在於 Java 中的泛型這一律念提出的目的,其只是做用於代碼編譯階段。在編譯過程當中,對於正確檢驗泛型結果後,會將泛型的相關信息擦除。也就是說,成功編譯事後的 class 文件中是不包含任何泛型信息的。泛型信息不會進入到運行時階段。

泛型類型在邏輯上看以當作是多個不一樣的類型,實際上都是相同的基本類型。

通配符

Ingeter 是 Number 的一個子類,同時 Generic<Ingeter>Generic<Number> 其實是相同的一種基本類型。那麼問題來了,在使用 Generic<Number> 做爲形參的方法中,可否使用Generic<Ingeter> 的實例傳入呢?在邏輯上相似於 Generic<Number>Generic<Ingeter> 是否能夠當作具備父子關係的泛型類型呢?下面咱們經過定義一個方法來驗證。

public void show(Generic<Number> obj) {
    System.out.println("key value is " + obj.getKey());
}
複製代碼

進行以下的調用:

Generic<Integer> genericInteger = new Generic<Integer>(123);

show(genericInteger);  //error Generic<java.lang.Integer> cannot be applied to Generic<java.lang.Number>
複製代碼

經過提示信息咱們能夠看到 Generic<Integer> 不能被看做爲 Generic<Number> 的子類。由此能夠看出:同一種泛型能夠對應多個版本(由於參數類型是不肯定的),不一樣版本的泛型類實例是不兼容的。

咱們不能所以定義一個 show(Generic<Integer> obj)來處理,所以咱們須要一個在邏輯上能夠表示同時是Generic和Generic父類的引用類型。由此類型通配符應運而生。

T、K、V、E 等泛型字母爲有類型,類型參數賦予具體的值。除了有類型,還能夠用通配符來表述類型, 未知類型,類型參數賦予不肯定值,任意類型只能用在聲明類型、方法參數上,不能用在定義泛型類上。將方法改寫成以下:

public void show(Generic<?> obj) {
    System.out.println("key value is " + obj.getKey());
}
複製代碼

此處 ? 是類型實參,而不是類型形參。即和 Number、String、Integer 同樣都是實際的類型,能夠把 當作全部類型的父類,是一種真實的類型。能夠解決當具體類型不肯定的時候,這個通配符就是 ?;當操做類型時,不須要使用類型的具體功能時,只使用 Object 類中的功能。那麼能夠用 ? 通配符來表未知類型。

泛型上下邊界

在使用泛型的時候,咱們還能夠爲傳入的泛型類型實參進行上下邊界的限制,如:類型實參只准傳入某種類型的父類或某種類型的子類。爲泛型添加上邊界,即傳入的類型實參必須是指定類型的子類型。

public void show(Generic<? extends Number> obj) {
    System.out.println("key value is " + obj.getKey());
}
複製代碼

咱們在泛型方法的入參限定參數類型爲 Number 的子類。

Generic<String> genericString = new Generic<String>("11111");
Generic<Integer> genericInteger = new Generic<Integer>(2222);


showKeyValue1(genericString); // error
showKeyValue1(genericInteger);

複製代碼

當咱們的入參爲 String 類型時,編譯報錯,由於 String 類型並非 Number 類型的子類。

類型通配符上限經過形如 Generic<? extends Number> 形式定義;相對應的,類型通配符下限爲Generic<? super Number>形式,其含義與類型通配符上限正好相反,在此不做過多闡述。

泛型數組

在 java 中是不能建立一個確切的泛型類型的數組的,即:

List<String>[] ls = new ArrayList<String>[10];  
複製代碼

如上會編譯報錯,而使用通配符建立泛型數組是能夠的:

List<?>[] ls = new ArrayList<?>[10]; 

//List<String>[] ls = new ArrayList[10];
複製代碼

JDK1.7 對泛型的簡化,因此另外一種聲明也是能夠的。

因爲JVM泛型的擦除機制,在運行時 JVM 是不知道泛型信息的。泛型數組實際的運行時對象數組只能是原始類型( T[]爲Object[],Pair[]爲Pair[] ),而實際的運行時數組對象多是T類型( 雖然運行時會擦除成原始類型 )。成功建立泛型數組的惟一方式就是建立一個被擦出類型的新數組,而後對其轉型。

public class GenericArray<T> {
    private Object[] array;  //維護Object[]類型數組
    @SupperessWarning("unchecked")
    public GenericArray(int v) {
        array = new Object[v];
    }
    public void put(int index, T item) {
        array[index] = item;
    }
    public T get(int index) { 
    	return (T)array[index]; 
    } //數組對象出口強轉
    public T[] rep() { return (T[])array; } //運行時不管怎樣都是Object[]類型 
    public static void main (String[] args){
        GenericArray<Integer> ga = new GenericArray<Integer>(10);
        // Integer[] ia = ga.rep(); //依舊ClassCastException
        Object[] oa = ga.rep(); //只能返回對象數組類型爲Object[]
        ga.put(0, 11);
        System.out.println(ga.get(0)); // 11
    }
}
複製代碼

在運行時,數組對象的出口作轉型輸出,入口方法在編譯期已實現類型安全,因此出口方法能夠放心強制類型轉換,保證成功。

小結

本文主要講了 Java 泛型的相關概念和應用。泛型使編譯器能夠在編譯期間對類型進行檢查以提升類型安全,減小運行時因爲對象類型不匹配引起的異常。由泛型的誕生介紹相關的概念,在保證代碼質量的狀況下,如何使用泛型去簡化開發。

訂閱最新文章,歡迎關注個人公衆號

微信公衆號

參考

java 泛型詳解

相關文章
相關標籤/搜索