Java泛型學習

泛型是不可變的(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或其子類的對象。

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

推薦閱讀更多精彩內容

  • 所謂泛型,就是變量類型的參數化。泛型是java1.5中引入的一個重要特征,通過引入泛型,可以使編譯時類型安全,運行...
    cvmars閱讀 219評論 0 2
  • 原生態類型 在沒有泛型之前,如果我們要維護一個價格列表: 當有人插入非法數據時,編譯也不會報異常 但是執行的時候,...
    小小浪把_Dont_know拍閱讀 234評論 0 0
  • 泛型是Java 1.5引入的新特性。泛型的本質是參數化類型,這種參數類型可以用在類、變量、接口和方法的創建中,分別...
    何時不晚閱讀 3,057評論 0 2
  • 作者:桃李君言 電影《奇異博士》,制作精良,特效逼真,場景酷炫,動作充滿力量,很抓人眼球,觀眾有很好的觀賞體驗。 ...
    陶薰讀書閱讀 963評論 9 4
  • 很多東西, 就掌握在自己手中。 比如快樂,你不快樂, 誰會同情你的悲傷; 比如堅強,你不堅強, 誰會憐憫你的懦弱;...
    雨妹妹_閱讀 145評論 0 0