泛型不是協變的,數組與集合類之間的區別##
雖然將集合看作是數組的抽象會有所幫助,但是數組還有一些集合不具備的特殊性質。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(); // 編譯不通過
}