泛型是不可變的(invariant)。
不可變,即如果A是B的子類,則A的泛型(例如List<A>)并非B的泛型(例如List<B>)的子類。例如下面的代碼是不合法的,編譯時會報錯:
//error:Type mismatch: cannot convert from ArrayList<Integer> to List<Number>
List<Number> list = new ArrayList<Integer>();
理解不可變的泛型前,我們需要先了解協變的數組。
數組是協變(convariant)的,如果如果A是B的子類,則A[]是B[]的子類。例如下面的代碼是合法的。
Number[] numberArray = new Integer[5];
問題來了,
為什么Java語言中數組是協變的,但泛型是不可變的?
首先明確一點,協變的容器是有隱患的,例如:
Number[] numberArray = new Integer[5];
//將Float對象賦值到Integer數組,運行時會報錯,Throw ArrayStoreException
numberArray[0] = 1.1;
對于數組而言,由于數組本身存儲了數組元素的類型信息,可以在運行時,進行類型檢查,所以上述的隱患并不可怕。
但是對于泛型而言,由于采用“擦除”技術,在運行時,泛型容器中并不存儲任何類型信息,無法進行安全檢查,泛型只有編譯期間的類型安全檢查。為了避免上述隱患,泛型被設計成不變的,在編譯期徹底杜絕上述錯誤隱患。
總結如下:
** 數組具有編譯時和運行時檢查,所以設計成協變的,并無大礙,編譯器未處理的隱患可在運行時處理。泛型只有編譯時檢查,所以設計成不變的,徹底杜絕隱患。**
其實,很多Java專家認為數組的協變設計是有缺陷的。個人以為,對于協變的缺陷,可以這樣理解:類型A和類型A的容器顯然是不同的東西,類型A和類型A容器的操作也顯然完全不同,所以,將類型A的繼承關系引入到類型A的容器中,在語義上并不合適;只是出于操作的便利性,各種語言中允許了這種做法。
可以利用通配符?實現泛型的向上轉型##
泛型是不可變的,A是B的子類,但A的泛型(例如List<A>)無法向上轉型為B的泛型(例如List<B>)。實際編程中,出于便利的考慮,我們希望泛型可以支持向上轉型,利用通配符?可以實現該目的,如下
List<Integer> integerList = new ArrayList<Integer>();
List<? extends Number> numberList = integerList;
integerList.add(5);
//下面一句代碼編譯時報錯,不能向帶通配符的泛型容器中添加非null的對象。
//numberList.add(5);
Number a = numberList.get(0);
其中,<? extends Number>的意思是:繼承自Number的某種特定類型。
注意,雖然利用通配符實現了向上轉型,但是為了避免“向容器中添加不匹配的對象”的問題,帶通配符的泛型容器不支持(除null以外的)添加對象操作。
不過,我們可以從帶通配符的泛型容器中獲取對象并使用。因為,例如在這個例子中,我們明確知道,numberList.get(0)返回的一定是Number或其子類的對象。