瞭解Java泛型

Java泛型是從JDK1.5開始引入的,它與C++的模版非常類似。但是Java的模版完全在編譯的時候實現,使用一種擦除的技術在編譯的時候就將模版完全替換,由編譯器執行類型檢查和推斷,編譯成普通的非泛型字節碼。正因爲如此,雖然Java泛型是Java類型安全的一大進步,但是使用起來的時候可能會遇到各種非常困擾的問題。

Java泛型不協變

在Java中,因爲Number是Integer的父類,所以Integer也是Number,不僅如此,而且Integer[]也Number[]。所以下面這段代碼是沒有任何問題的:

Number[] nums = new Integer[5];

這就是協變,因爲Integer是Number,所以Integer[]也是Number[]。Java的數組是支持協變的。但是Java泛型時不支持協變的。所以下面這段代碼將會出現編譯錯誤。

List<Number> list = new ArrayList<Integer>(); //compile error

如果想要編譯通過,這需要將List的申明改成如下

List<? extends Number> list = new ArrayList<Integer>();  //ok

這是通過***通配符引用***來解決這個編譯不通過的問題,現在上面那段代碼的泛型類型就是TypeVariable了。Integer也是符合? extends Number的。

不能實例化泛型數組

Java泛型的不協變導致的一個問題就是不能實例化泛型數組。因爲數組是協變的,但是泛型不協變。這樣如果可以實例化泛型數組,那麼就會導致問題的出現,看下面這段代碼

List<Character>[] listArr = new List<Character>[10]; // illegal, 非法,假設可以通過編譯
Object[] oa = listArr;       //數組協變,這句沒有問題
oa[0] = new ArrayList<Integer>();    // 看似沒問題,但已經出問題了

上面最後一句已經是有問題了,oa中每個元素的類型時List,但是現在卻將ArrayList賦值給了它。

不能夠實例化泛型數組,但是這種通配符類型(List<?>)除外,如:

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

構造

構造變量

我們不能直接new 泛型數組,也不能夠直接實例化一個泛型類型對象,比如下面這種也是不能夠通過編譯的:

T t = new T();  //error

對於泛型的初始化,可以考慮clone:

static <T extends Cloneable> void create(T t1){
    T t = t1.clone();
}

但是必須將clone 的改爲public。

構造數組

對於數組,我們可以通過下面的方式創新新的數組

V[] arr = (V[]) new Object[10]

這種方式通過強制轉換Object[]爲V[],但是太彆扭,並不推薦這種方式。或者通過每次使用V的時候,將Object[]讀取的時候進行強制轉換。

最好的辦法是向構造函數傳遞類文字(Foo.class),這樣,該實現就能在運行時知道 T的值。不採用這種方法的原因在於向後兼容性 —— 新的泛型集合類不能與 Collections 框架以前的版本兼容。

所以在Collections中也有這麼一種方式實現構造數組

ArrayList<V> implements List<V> {
    
    public ArrayList(Class<V> elementType){
        blockArray = (V[])Array.newInstance(elementType,DEFAULT_LENGTH);
    }
}

其中Array.newInstance返回的是Object[],這是爲了向下兼容而做的妥協。

擦除也是造成上述構造問題的原因,即不能創建泛型類型的對象,因爲編譯器不知道要調用什麼構造函數。如果泛型類需要構造用泛型類型參數來指定類型的對象,那麼構造函數應該接受類文字(Foo.class)並將它們保存起來,以便通過反射創建實例。

Java擦除

Java的泛型是在編譯的時候就被擦除的,因爲泛型基本上都是在 Java 編譯器中而不是運行庫中實現的,所以在生成字節碼的時候,差不多所有關於泛型類型的類型信息都被“擦掉”了。換句話說,編譯器生成的代碼與您手工編寫的不用泛型、檢查程序的類型安全後進行強制類型轉換所得到的代碼基本相同。如List與List這兩個類型是一樣的,也因爲擦出,instanceof在範型是沒有任何意義的。

另外強制轉換也是沒什麼作用,比如下面這段代碼:

public static <T> T nativeCast(Object o){
    return (T)o;
}

實際上編譯之後T就被擦出了,其實就是直接返回o(是Object類型)。

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