深入理解java的泛型

簡介

泛型是JDK 5引入的概念,泛型的引入主要是爲了保證java中類型的安全性,有點像C++中的模板。

但是Java爲了保證向下兼容性,它的泛型全部都是在編譯期間實現的。編譯器執行類型檢查和類型推斷,然後生成普通的非泛型的字節碼。這種就叫做類型擦除。編譯器在編譯的過程中執行類型檢查來保證類型安全,但是在隨後的字節碼生成之前將其擦除。

這樣就會帶來讓人困惑的結果。本文將會詳細講解泛型在java中的使用,以避免進入誤區。

泛型和協變

有關協變和逆變的詳細說明可以參考:

深入理解協變和逆變

這裏我再總結一下,協變和逆變只有在類型聲明中的類型參數裏纔有意義,對參數化的方法沒有意義,因爲該標記影響的是子類繼承行爲,而方法沒有子類。

當然java中沒有顯示的表示參數類型是協變還是逆變。

協變意思是如果有兩個類 A<T> 和 A<C>, 其中C是T的子類,那麼我們可以用A<C>來替代A<T>。

逆變就是相反的關係。

Java中數組就是協變的,比如Integer是Number的子類,那麼Integer[]也是 Number[]的子類,我們可以在需要 Number[] 的時候傳入 Integer[]。

接下來我們考慮泛型的情況,List<Number> 是不是 List<Integer>的父類呢?很遺憾,並不是。

我們得出這樣一個結論:泛型不是協變的。

爲什麼呢?我們舉個例子:

List<Integer> integerList = new ArrayList<>();
        List<Number> numberList = integerList; // compile error
        numberList.add(new Float(1.111));

假如integerList可以賦值給numberList,那麼numberList可以添加任意Number類型,比如Float,這樣就違背了泛型的初衷,向Integer list中添加了Float。所以上面的操作是不被允許的。

剛剛我們講到Array是協變的,如果在Array中帶入泛型,則會發生編譯錯誤。比如new List<String>[10]是不合法的,但是 new List<?>[10]是可以的。因爲在泛型中?表示的是未知類型。


List<?>[] list1 = new List<?>[10];

List<String>[] list2 = new List<String>[10]; //compile error

泛型在使用中會遇到的問題

因爲類型擦除的原因,List<String>和List<Integer>在運行是都會被當做成爲List。所以我們在使用泛型時候的一些操作會遇到問題。

假如我們有一個泛型的類,類中有一個方法,方法的參數是泛型,我們想在這個方法中對泛型參數進行一個拷貝操作。

public class CustUser<T> {

    public void useT(T param){
        T copy = new T(param);  // compile error
    }
}

上面操作會編譯失敗,因爲我們並不知道T是什麼,也不知道T到底有沒有相應的構造函數。

直接clone T是沒有辦法了,如果我們想copy一個Set,set中的類型是未定義的該怎麼做呢?

    public void useTSet(Set<?> set){
        Set<?> copy1 = new HashSet<?>(set);  // compile error
        Set<?> copy2 = new HashSet<>(set);
        Set<?> copy3 = new HashSet<Object>(set);  
    }

可以看到?是不能直接用於實例化的。但是我們可以用下面的兩種方式代替。

再看看Array的使用:

    public void useArray(){
         T[] typeArray1= new T[20];  //compile error
        T[] typeArray2=(T[]) new Object[20];
        T[] typeArray3 = (T[]) Array.newInstance(String.class, 20);
    }

同樣的,T是不能直接用於實例化的,但是我們可以用下面兩種方式代替。

類型擦除要注意的事項

因爲類型擦除的原因,我們在接口實現中,實現同一個接口的兩個不同類型是無意義的:

public class someClass implements Comparable<Number>, Comparable<String> { ... } // no

因爲在編譯過後的字節碼看來,兩個Comparable是一樣的。

同樣的,我們使用T來做類型強制轉換也是沒有意義的:

public <T> T cast(T t, Object o) { return (T) o; }

因爲編譯器並不知道這個強制轉換是對還是錯。

總結

本文討論了泛型在java中使用中可能會存在的問題,希望大家能夠喜歡。

本文的例子https://github.com/ddean2009/learn-java-collections

本文作者:flydean程序那些事

本文鏈接:http://www.flydean.com/java-generics-in-deep/

本文來源:flydean的博客

歡迎關注我的公衆號:程序那些事,更多精彩等着您!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章