相信很多人和我一樣,接觸Java多年,卻仍舊搞不清楚 Java 泛型中 <?>和 <? extends Object>的相似和不同,但是,這應該是一個比較高端大氣上檔次的Question, 在我們進行深入的探討之前,有必要對Java泛型有一個基礎的了解,詳細請看上一篇文章!
重溫Java泛型,帶你更深入地理解它,更好的使用它!
1. 泛型產生的背景
在 JDK5 中引入了泛型來消除編譯時錯誤和加強型別安全性,這種額外的型別安全性消除了某些用例中的強制轉換,并使程式員能夠撰寫泛型演算法,這兩種方法都可以生成更具可讀性的代碼,
例如,在 JDK5 之前,我們必須使用強制轉換來處理串列的元素,這反過來又產生了一類特定的運行時錯誤:
List aList = new ArrayList();
aList.add(new Integer(1));
aList.add("a_string");
for (int i = 0; i < aList.size(); i++) {
Integer x = (Integer) aList.get(i);
}
現在,我們想解決兩個問題:
- 我們需要一個顯式轉換來從
aList中提取值——型別取決于左側的變數型別(在本例中為Integer) - 當我們試圖將
a_string轉換為Integer時,在第二次迭代中會出現運行時錯誤,
泛型填補了這個空白,代碼如下:
List<Integer> iList = new ArrayList<>();
iList.add(1);
iList.add("a_string"); // compile time error
for (int i = 0; i < iList.size(); i++) {
int x = iList.get(i);
}
執行上述代碼,編譯器會告訴我們,無法將 a_string 添加到 Integer 型別的 List 中,這比起在運行時才發現例外要好很多,
而且,不需要顯式轉換,因為編譯器已經知道 iList 包含 Integer型別的資料,另外,由于自動拆箱的關系,我們甚至不需要使用 Integer 型別,它的原始型別就足夠了,
2. 泛型中的通配符
問號或通配符在泛型中用來表示未知型別,它可以有三種形式:
- 無界通配符: List<?> 表示未知型別的串列
- 上界通配符:List<? extends Number> 表示 Number 或其子型別(如Integer和Double)的串列
- 下界通配符:List<? super Integer> 表示Integer或其超型別Number和Object的串列
由于 Object 是 Java 中所有型別的固有超類,所以我們會認為它也可以表示未知型別,換句話說,List<?> 和List<Object> 可以達到相同的目的,但事實并非如此,
來看看這兩個方法:
public static void printListObject(List<Object> list) {
for (Object element : list) {
System.out.print(element + " ");
}
}
public static void printListWildCard(List<?> list) {
for (Object element: list) {
System.out.print(element + " ");
}
}
給出一個整數的串列,比如:
List<Integer> li = Arrays.asList(1, 2, 3);
執行 printListObject(li) 不會編譯,并且我們將得到以下錯誤:
The method printListObject(List<Object>) is not applicable for the arguments (List<Integer>)
而執行 printListWildCard(li) 將通過編譯,并將 1 2 3 輸出到控制臺,
3. <?>和<? extends Object>的相同之處
在上面的示例中,如果我們將 printListWildCard 方法更改為:
public static void printListWildCard(List<? extends Object> list)
它的作業方式與 printListWildCard(List<?>)相同,這是因為 Object 是 Java 所有物件的超類,基本上所有的東西都擴展了Object,因此,這個方法也會處理一個 Integer 型別的List,
也就是說, <?> 和 <? extends Object> 在這個例子中是同一個意思,
雖然在大多數情況下,這是正確的,但也有一些區別,接下來我們就來看看它們之間的差異,
4. <?>和<? extends Object>的不同之處
可重構型別是指那些在編譯時未被擦除的型別,換句話說,一個不可重構型別,運行時將比編譯時表達的資訊更少,因為其中一些資訊會被擦除,
一般來說,引數化型別是不可重新定義的,比如 List<String> 和 Map<Integer,String> 就不可重新定義,編譯器會擦除它們的型別,并將它們分別視為串列和映射,
這個準則的唯一例外是無界通配符型別,也就是說, List<?> 以及 Map<?, ?> 是可重寫的,
另外,List<? extends Object> 不可重寫,雖然微妙,但這是一個顯著的區別,
不可重構的型別在某些情況下不能使用,例如在 instanceof 運算子或作為陣列的元素,
所以,如果我們的代碼寫成這樣:
List someList = new ArrayList<>();
boolean instanceTest = someList instanceof List<?>
代碼編譯后,instanceTest 為true,
但是,如果我們在 List<? extends Object> 上使用 instanceof 運算子:
List anotherList = new ArrayList<>();
boolean instanceTest = anotherList instanceof List<? extends Object>;
那么第2行不編譯,
類似地,在下面的代碼片段中,第1行編譯,但第2行不編譯:
List<?>[] arrayOfList = new List<?>[1];
List<? extends Object>[] arrayOfAnotherList = new List<? extends Object>[1]
5.結語
好了,文章到此就劃上句號了,在本文中,我們主要討論了<?> 和 <? extends Object>的異同,雖然基本上是相似的,但兩者在可變與否方面存在細微差異,
如果你覺得文章還不錯,記得關注公眾號: 鍋外的大佬
劉一手的博客
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/224421.html
標籤:Java
上一篇:C 語言-運算子(算術運算子,型別轉換,賦值運算子,自增,自減,sizeof 運算子,逗號運算子,關系運算子,邏輯運算子,三目運算子)
下一篇:Java 型別資訊詳解和反射機制
