泛型不是協(xié)變的矫夷,數(shù)組與集合類之間的區(qū)別##
雖然將集合看作是數(shù)組的抽象會有所幫助,但是數(shù)組還有一些集合不具備的特殊性質(zhì)看尼。Java 語言中的數(shù)組是協(xié)變的(covariant)淑倾,也就是說河狐,如果 Integer擴(kuò)展了 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]);
您也許認(rèn)為這一原理同樣適用于泛型類型 —— List<Number>是 List<Integer>的超類型,那么可以在需要 List<Number>的地方傳遞 List<Integer>奠支。不幸的是馋辈,情況并非如此。
不允許這樣做有一個很充分的理由:這樣做將破壞要提供的類型安全泛型胚宦。如果能夠?qū)?List<Integer>賦給 List<Number>首有。那么下面的代碼就允許將非 Integer的內(nèi)容放入 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的別名,那么這就破壞了蘊(yùn)含在 li定義中的類型安全承諾 —— 它是一個整數(shù)列表卜壕,這就是泛型類型不能協(xié)變的原因您旁。
一個常見錯誤##
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的泛型機(jī)制是在編譯級別實現(xiàn)的驼鞭。編譯器生成的字節(jié)碼在運(yùn)行期間并不包含泛型的類型信息。
在編譯之后尺碰,List<Object>和List<String>將變成List挣棕,Object和String類型信息對于JVM來說是不可見的。在編譯階段亲桥,編譯器發(fā)現(xiàn)它們不一致洛心,因此給出了一個編譯錯誤。
通配符和有界通配符##
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.
}
}
擦除的實現(xiàn)##
因為泛型基本上都是在 Java 編譯器中而不是運(yùn)行庫中實現(xiàn)的题篷,所以在生成字節(jié)碼的時候词身,差不多所有關(guān)于泛型類型的類型信息都被“擦掉”了。換句話說番枚,編譯器生成的代碼與您手工編寫的不用泛型法严、檢查程序的類型安全后進(jìn)行強(qiáng)制類型轉(zhuǎn)換所得到的代碼基本相同。與 C++ 不同户辫,List<Integer>和 List<String>是同一個類(雖然是不同的類型但都是 List<?>的子類型渐夸,與以前的版本相比,在 JDK 5.0 中這是一個更重要的區(qū)別)渔欢。
擦除意味著一個類不能同時實現(xiàn) Comparable<String>和 Comparable<Number>墓塌,因為事實上兩者都在同一個接口中,指定同一個 compareTo()方法奥额。聲明 DecimalString類以便與 String與 Number比較似乎是明智的苫幢,但對于 Java 編譯器來說,這相當(dāng)于對同一個方法進(jìn)行了兩次聲明:
public class DecimalString implements Comparable<Number>, Comparable<String>
{
@Override
public int compareTo(Number o) {
return 0;
}
} // nope
擦除的另一個后果是垫挨,對泛型類型參數(shù)是用強(qiáng)制類型轉(zhuǎn)換或者 instanceof毫無意義韩肝。下面的代碼完全不會改善代碼的類型安全性:
public <T> T naiveCast(T t, Object o) {
return (T) o;
}
編譯器僅僅發(fā)出一個類型未檢查轉(zhuǎn)換警告,因為它不知道這種轉(zhuǎn)換是否安全九榔。naiveCast()方法實際上根本不作任何轉(zhuǎn)換哀峻,T直接被替換為 Object,與期望的相反哲泊,傳入的對象被強(qiáng)制轉(zhuǎn)換為 Object剩蟀。
擦除也是造成上述構(gòu)造問題的原因,即不能創(chuàng)建泛型類型的對象切威,因為編譯器不知道要調(diào)用什么構(gòu)造函數(shù)育特。如果泛型類需要構(gòu)造用泛型類型參數(shù)來指定類型的對象,那么構(gòu)造函數(shù)應(yīng)該接受類文字(Foo.class)并將它們保存起來先朦,以便通過反射創(chuàng)建實例缰冤。
public static <T> T getTypeInstance() {
return new T(); // 編譯不通過
}