一、概要
java範圍有三種形式:
- <? extends T>: 上界通配符, ?表示繼承自T的類(沿着類圖,上邊界是T)。頻繁往外讀取内容,适合采用上界通配符。
- <? super T>:下界通配符,?表示T及其父類(沿着類圖,下邊界是T)。頻繁插入内容,适合采用下界通配符。
- <?>:某個類型。單純表示引用某一類型,不進行插入或讀取
- 如果頻繁讀取或插入,盡量避免使用通配符,以免資料丢失。
二、建構類樹
為了清晰表明概要中的意識,先建構類樹,用于概念細化、代碼解讀。此處解讀參考Think In Java書的案例。
class Fruit {
public void getClassName() {System.out.println(this.getClass().getSimpleName());}
}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Maigold extends Apple {}
class Orange extends Fruit { }
class Mandarin extends Orange {}
class Tangerine extends Orange {}

以上即為本次案例所使用的類圖及繼承關系
如上圖,
- <? extends Apple>為上界通配符,即上界是Apple,?表示繼承自Apple的類,作用範圍是紅色三角内部的類
- <? super Apple>為下界通配符,即下界是Apple,?表示Apple及其父類,作用範圍是藍色三角内部的類。
三、代碼解讀
3.1 泛型容器無法進行向上轉型
import java.util.ArrayList;
import java.util.List;
public class NonCovariantGenerics {
// 編譯錯誤,無法進行向上轉型
List<Fruit> flist = new ArrayList<Apple>();
}
上述代碼會引發編譯錯誤,無法将List<Apple>向上轉型為List<Fruit>,即無法将“涉及Apple的範型賦給涉及Fruit的範型”。當然這違背生活常識,Apple竟然無法放到Fruit裡。但從代碼角度思考,Apple的List不是Fruit的List,因為List<Apple>将持有Apple及其子類,而List<Fruit>能夠持有Fruit子類,範圍遠大于List<Apple>。
如果想在泛型容器間建立轉型關系,使代碼描述更加人性化,即面向對象,則需要采取通配符。
3.2 上界通配符 <? extends T>
import java.util.ArrayList;
import java.util.List;
public class GenericsAndCovariance {
public static void main(String[] args) {
// 上界通配符進行容器的向上轉型
List<? extends Fruit> flist = new ArrayList<Apple>();
// 以下均會引起編譯錯誤
// flist.add(new Apple());
// flist.add(new Fruit());
// flist.add(new Object());
flist.add(null); // Legal but uninteresting
// We know that it returns at least Fruit:
Fruit f = flist.get(0);
f.getClassName(); //List裡隻有空指針,無法調用該函數
}
}
通過上述代碼例子得知,List<? extends Fruit>并不是說該List可以持有任何類型的Fruit,通配符引用的是明确的類型。是以,該處可以這樣解譯“List<? extends Fruit> 持有某個具體類型,該類型具體是什麼我不清楚,但我可以具定它是Fruit的子類型”。
可見,因為無法确定List<? extends Fruit>具體持有什麼類型,故向内部填加對象比較危險,故調用add(Object object)會引發失入。但是可以使用get(int index)方法。
可見,上界通配符主要支援讀取操作,應盡量避免寫入操作,以免引發異常。
3.3 繞過編譯器
import java.util.*;
public class CompilerIntelligence {
public static void main(String[] args) {
List<? extends Fruit> flist =
Arrays.asList(new Apple(), new Fruit(), new Orange(), new Jonathan());
Apple apple = (Apple)flist.get(0); // 利用get擷取類
apple.getClassName();
for (int i = 1; i < flist.size(); i++) {
Fruit temp = flist.get(i); //利用動态編譯,指向不同的類
temp.getClassName();
}
flist.contains(new Apple()); // 調用boolean contains(Object o);
flist.indexOf(new Apple()); // 調用int indexOf(Object o);
//flist.add(new Apple()); //調用boolean add(E e);失敗
}
}
/**
* OUTPUT
* Class name is : Apple
* Class name is : Fruit
* Class name is : Orange
* Class name is : Jonathan
*/
上述代碼可知:
- 借助Arrays.asList将Fruit及其子類,以List的形式存儲到List<? extends Fruit>,變相實作了add,隻是有些機械化。
- 利用強制轉型,擷取對應的Apple類型
- 利用動态編譯,動态引用Orange, Jonathan類型
- contains 和indexOf方法的參數是Object,不涉及通配符,是以可以成功執行
- add的方法參數是泛型,在本案例中即是<? extends Fruit>,是通配符,無法明确具體類型,是以編譯失敗
3.4 下界通配符<? super T>
下界通配符,直白些,可以了解為支援向下轉型,其作用域可以參見本篇第二章圖的藍色區域
import java.util.*;
public class SuperTypeWildcards {
public static void main(String[] args ) {
//支援向下轉型,不支援向上轉型
List<? super Apple> appleList = new ArrayList<Fruit>();
//List<? super Apple> appleListTwo = new ArrayList<Jonathan>();轉型失敗
//appleList = new ArrayList<Jonathan>();轉型失敗,無法進行
appleList.add(new Apple());
//appleList.add(new Fruit());編譯失敗,List<Fruit>可以向下轉型為List<? super Apple>,
// 但是轉型後,就隻能放Apple相關類了
appleList.add(new Jonathan());
appleList.add(new Maigold());
//appleList.add(new Orange());編譯失敗,Orange不是Apple
for (int i = 0; i < appleList.size(); i++) {
Apple temp = (Apple) appleList.get(i);
temp.getClassName();
}
}
}
/**OUTPUT
* Class name is : Apple
* Class name is : Jonathan
* Class name is : Maigold
*/
上述代碼得知
- <? super T>支援向下轉型,不支援向上轉型,作用範圍參見本篇圖2。我把這了解為下界通配符的通俗含義
- <? super T>支援插入資料類,插入的資料必須是Apple及其子類
- <? super T>通過強制類型轉換,也可以取資料,可能會存在資料丢失情況
3.5 無界通配符 <?>
用一句歌詞來表達無界通配符,“我不知道你是誰,但我知道你為了誰”。無界通配符作用等價于使用原生類型,其表明使用泛型進行類型引用,在不清楚或者無需清楚具體類型的前提下。