泛型不是協(xié)變的,數(shù)組與集合類之間的區(qū)別##
雖然將集合看作是數(shù)組的抽象會(huì)有所幫助,但是數(shù)組還有一些集合不具備的特殊性質(zhì)。Java 語(yǔ)言中的數(shù)組是協(xié)變的(covariant),也就是說(shuō),如果 Integer擴(kuò)展了 Number(事實(shí)也是如此),那么不僅 Integer是 Number,而且 Integer[]也是 Number[],在要求 Number[]的地方完全可以傳遞或者賦予 Integer[]。(更正式地說(shuō),如果 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 編譯不通過(guò),因?yàn)閕ntArray類型為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>。不幸的是,情況并非如此。
不允許這樣做有一個(gè)很充分的理由:這樣做將破壞要提供的類型安全泛型。如果能夠?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));
因?yàn)?ln是 List<Number>,所以向其添加 Float似乎是完全合法的。但是如果 ln是 li的別名,那么這就破壞了蘊(yùn)含在 li定義中的類型安全承諾 —— 它是一個(gè)整數(shù)列表,這就是泛型類型不能協(xié)變的原因。
一個(gè)常見(jiàn)錯(cuò)誤##
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); //編譯不過(guò)
}
public static void accept(ArrayList<Object> al) {
for (Object o : al)
System.out.println(o);
}
}
以上代碼看起來(lái)是沒(méi)問(wèn)題的,因?yàn)镾tring是Object的子類。然而,這并不會(huì)工作,編譯不會(huì)通過(guò)
原因在于類型擦除。記?。篔ava的泛型機(jī)制是在編譯級(jí)別實(shí)現(xiàn)的。編譯器生成的字節(jié)碼在運(yùn)行期間并不包含泛型的類型信息。
在編譯之后,List<Object>和List<String>將變成List,Object和String類型信息對(duì)于JVM來(lái)說(shuō)是不可見(jiàn)的。在編譯階段,編譯器發(fā)現(xiàn)它們不一致,因此給出了一個(gè)編譯錯(cuò)誤。
通配符和有界通配符##
List<? >表示List能包含任何類型的元素
public static void main(String[] args) {
ArrayList<String> al = new ArrayList<String>();
al.add("a");
al.add("b");
// accept(al); //編譯不過(guò)
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.
}
}
擦除的實(shí)現(xiàn)##
因?yàn)榉盒突旧隙际窃?Java 編譯器中而不是運(yùn)行庫(kù)中實(shí)現(xiàn)的,所以在生成字節(jié)碼的時(shí)候,差不多所有關(guān)于泛型類型的類型信息都被“擦掉”了。換句話說(shuō),編譯器生成的代碼與您手工編寫(xiě)的不用泛型、檢查程序的類型安全后進(jìn)行強(qiáng)制類型轉(zhuǎn)換所得到的代碼基本相同。與 C++ 不同,List<Integer>和 List<String>是同一個(gè)類(雖然是不同的類型但都是 List<?>的子類型,與以前的版本相比,在 JDK 5.0 中這是一個(gè)更重要的區(qū)別)。
擦除意味著一個(gè)類不能同時(shí)實(shí)現(xiàn) Comparable<String>和 Comparable<Number>,因?yàn)槭聦?shí)上兩者都在同一個(gè)接口中,指定同一個(gè) compareTo()方法。聲明 DecimalString類以便與 String與 Number比較似乎是明智的,但對(duì)于 Java 編譯器來(lái)說(shuō),這相當(dāng)于對(duì)同一個(gè)方法進(jìn)行了兩次聲明:
public class DecimalString implements Comparable<Number>, Comparable<String>
{
@Override
public int compareTo(Number o) {
return 0;
}
} // nope
擦除的另一個(gè)后果是,對(duì)泛型類型參數(shù)是用強(qiáng)制類型轉(zhuǎn)換或者 instanceof毫無(wú)意義。下面的代碼完全不會(huì)改善代碼的類型安全性:
public <T> T naiveCast(T t, Object o) {
return (T) o;
}
編譯器僅僅發(fā)出一個(gè)類型未檢查轉(zhuǎn)換警告,因?yàn)樗恢肋@種轉(zhuǎn)換是否安全。naiveCast()方法實(shí)際上根本不作任何轉(zhuǎn)換,T直接被替換為 Object,與期望的相反,傳入的對(duì)象被強(qiáng)制轉(zhuǎn)換為 Object。
擦除也是造成上述構(gòu)造問(wèn)題的原因,即不能創(chuàng)建泛型類型的對(duì)象,因?yàn)榫幾g器不知道要調(diào)用什么構(gòu)造函數(shù)。如果泛型類需要構(gòu)造用泛型類型參數(shù)來(lái)指定類型的對(duì)象,那么構(gòu)造函數(shù)應(yīng)該接受類文字(Foo.class)并將它們保存起來(lái),以便通過(guò)反射創(chuàng)建實(shí)例。
public static <T> T getTypeInstance() {
return new T(); // 編譯不通過(guò)
}