java泛型總結之二

代碼github地址

泛型不是協變的,數組與集合類之間的區別##

雖然將集合看作是數組的抽象會有所幫助,但是數組還有一些集合不具備的特殊性質。Java 語言中的數組是協變的(covariant),也就是說,如果 Integer擴展了 Number(事實也是如此),那么不僅 Integer是 Number,而且 Integer[]也是 Number[],在要求 Number[]的地方完全可以傳遞或者賦予 Integer[]。(更正式地說,如果 Number是 Integer的超類型,那么 Number[]也是 Integer[]的超類型)。

    Integer [] intArray = new Integer[10];
    intArray[0] = 10;
    Number[] numArray = intArray;

    numArray[2] = 3;
    numArray[1] = 2.5f;   // java.lang.ArrayStoreException: java.lang.Float 編譯不通過,因為intArray類型為Integer
    System.out.println("numArray[2] = " + numArray[2]);
    System.out.println("numArray[0] = " + numArray[0] + "; numArray[1] = " + numArray[1]);

您也許認為這一原理同樣適用于泛型類型 —— List<Number>是 List<Integer>的超類型,那么可以在需要 List<Number>的地方傳遞 List<Integer>。不幸的是,情況并非如此。
不允許這樣做有一個很充分的理由:這樣做將破壞要提供的類型安全泛型。如果能夠將 List<Integer>賦給 List<Number>。那么下面的代碼就允許將非 Integer的內容放入 List<Integer>:

List<Integer> li = new ArrayList<Integer>(); 
List<Number> ln = li; // illegal 
ln.add(new Float(3.1415));

因為 ln是 List<Number>,所以向其添加 Float似乎是完全合法的。但是如果 ln是 li的別名,那么這就破壞了蘊含在 li定義中的類型安全承諾 —— 它是一個整數列表,這就是泛型類型不能協變的原因。

一個常見錯誤##

import java.util.ArrayList;

/**
 * Created by shun on 2017/8/31.
 */
public class ErasureProblem {
    public static void main(String[] args) {
        ArrayList<String> al = new ArrayList<String>();
        al.add("a");
        al.add("b");
        // accept(al); //編譯不過
    }

    public static void accept(ArrayList<Object> al) {
        for (Object o : al)
            System.out.println(o);
    }

}

以上代碼看起來是沒問題的,因為String是Object的子類。然而,這并不會工作,編譯不會通過
原因在于類型擦除。記住:Java的泛型機制是在編譯級別實現的。編譯器生成的字節碼在運行期間并不包含泛型的類型信息。

在編譯之后,List<Object>和List<String>將變成List,Object和String類型信息對于JVM來說是不可見的。在編譯階段,編譯器發現它們不一致,因此給出了一個編譯錯誤。

通配符和有界通配符##

List<? >表示List能包含任何類型的元素

public static void main(String[] args) {
    ArrayList<String> al = new ArrayList<String>();
    al.add("a");
    al.add("b");
    // accept(al); //編譯不過

    test(al);
    ArrayList<Object> a = new ArrayList<>();
    a.add("abc");
    a.add(1);
    test(a);
}

public static void test(ArrayList<?> al) {
    for (Object e : al) {// no matter what type, it will be Object
        System.out.println(e);
        // in this method, because we don’t know what type ? is, we can not
        // add anything to al.
    }
}

擦除的實現##

因為泛型基本上都是在 Java 編譯器中而不是運行庫中實現的,所以在生成字節碼的時候,差不多所有關于泛型類型的類型信息都被“擦掉”了。換句話說,編譯器生成的代碼與您手工編寫的不用泛型、檢查程序的類型安全后進行強制類型轉換所得到的代碼基本相同。與 C++ 不同,List<Integer>和 List<String>是同一個類(雖然是不同的類型但都是 List<?>的子類型,與以前的版本相比,在 JDK 5.0 中這是一個更重要的區別)。
擦除意味著一個類不能同時實現 Comparable<String>和 Comparable<Number>,因為事實上兩者都在同一個接口中,指定同一個 compareTo()方法。聲明 DecimalString類以便與 String與 Number比較似乎是明智的,但對于 Java 編譯器來說,這相當于對同一個方法進行了兩次聲明:

public class DecimalString implements Comparable<Number>, Comparable<String>
{

    @Override
    public int compareTo(Number o) {
        return 0;
    }
} // nope

擦除的另一個后果是,對泛型類型參數是用強制類型轉換或者 instanceof毫無意義。下面的代碼完全不會改善代碼的類型安全性:

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

編譯器僅僅發出一個類型未檢查轉換警告,因為它不知道這種轉換是否安全。naiveCast()方法實際上根本不作任何轉換,T直接被替換為 Object,與期望的相反,傳入的對象被強制轉換為 Object。
擦除也是造成上述構造問題的原因,即不能創建泛型類型的對象,因為編譯器不知道要調用什么構造函數。如果泛型類需要構造用泛型類型參數來指定類型的對象,那么構造函數應該接受類文字(Foo.class)并將它們保存起來,以便通過反射創建實例。

public static <T> T getTypeInstance() {
    return new T(); // 編譯不通過
}

引用:

了解泛型
Java類型擦除機制

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 開發人員在使用泛型的時候,很容易根據自己的直覺而犯一些錯誤。比如一個方法如果接收List作為形式參數,那么如果嘗試...
    時待吾閱讀 1,073評論 0 3
  • 文章作者:Tyan博客:noahsnail.com 1. 什么是泛型 Java泛型(Generics)是JDK 5...
    SnailTyan閱讀 788評論 0 3
  • 第8章 泛型 通常情況的類和函數,我們只需要使用具體的類型即可:要么是基本類型,要么是自定義的類。但是在集合類的場...
    光劍書架上的書閱讀 2,160評論 6 10
  • 上線時對主要流程的回歸是必不可少的(內容穩定,重復勞動頻率高,但又十分重要);平時需要對線上情況的監控(曾經就出現...
    丟石頭閱讀 195評論 0 0
  • 《瑯琊榜》終于看完了。 最大感觸就是:君子報仇,十年不晚。 這是2015的電視劇,當時室友和周圍同學每天都等著更新...
    關小堯閱讀 239評論 0 1