Java泛型食用筆記(四) -- 通配符
1. 三種通配符
通配符為一個泛型類所指定的類型集合提供了一個有用的類型范圍催首,Java 里有三種通配符:
- 無限定通配符蚁趁, <?>
- 上界限定符裙盾, <? extends Number>
- 下界限定符, <? super Number>
上界限定符接受 extends 后面類的本身與其子類他嫡, 下界限定符接受 super 后面類的本身與其父類番官。無限定通配符接受任何類。
2. 無限定通配符
無限定通配符表示匹配任意類涮瞻。ArrayList<?>
和 ArrayList
鲤拿,ArrayList<Object>
看上去功能有點類似假褪,但實際卻不一樣署咽。 ArrayList<?>
是任意 ArrayList<T>
的超類,而我們知道 ArrayList<Object>
并不是生音。ArrayList<?>
雖然可以匹配任何類位他,我們并不知道那個類的類型悄蕾,但我們知道里面的所有元素都有相同的類。而原始類 ArrayList
可以添加任意不同類型的元素,編譯器并不能進(jìn)行類型判斷纽疟,但運(yùn)行的時候可能會拋出異常。ArrayList<Object>
明確的告訴我們可以添加任何類型的對象烂琴。
看一個無限定通配符例子
public class GenericTest07 {
public static void printCollection(Collection<Object> col1) {
for (Object obj : col1) {
System.out.println(obj);
}
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
printCollection(list); // compile error
}
}
由于 ArrayList<Integer>
不是 Collection<Object>
的子類弧械,故編譯不能通過。改用無限定通配符即可編譯通過:
public class GenericTest07 {
public static void printCollection(Collection<?> col1) {
for (Object obj : col1) {
System.out.println(obj);
}
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
printCollection(list);
}
}
Collection<?>
是任意 T ArrayList<T>
的超類譬挚,可以編譯通過锅铅。但是,在printCollection
方法中减宣,卻不能使用任何帶有類型參數(shù)的方法盐须,比如 add(T t)
, 而 remove(Object obj) contains(Object obj)
等方法都是可以調(diào)用的。
再看一個例子:
public class GenericTest08 {
public static void reBox(Box<?> box) {
System.out.println(box.put(box.get()));
}
}
public interface Box<T> {
public T get();
public void put(T t);
}
這段代碼初看應(yīng)該是 work 的漆腌,然而事實可能要讓你失望了贼邓,編譯會提示類型不兼容的錯誤阶冈。事實上,你根本就不能在此處調(diào)用 box.put()
方法塑径,因為box.put() 的形參類型是未知的女坑,編譯器不能檢驗?zāi)愕膶崊㈩愋褪欠衽c形參類型一致。
有沒有辦法實現(xiàn)這個邏輯的晓勇,我們需要借助一個輔助方法堂飞。
public class GenericTest08 {
public static void reBox(Box<?> box) {
reboxHelper(box);
}
private static <V> void reboxHelper(Box<V> box) {
box.put(box.get());
}
}
reboxHelper 方法幫助編譯器保留一部分類型信息。
上界限定符
我們知道绑咱,和數(shù)組不一樣绰筛,泛型并不是協(xié)變的。比如 Dog
是 Animal
的子類描融,那 Dog[]
也是 Animal[]
的子類铝噩,但 List<Dog>
并不是 List<Animal>
的子類。這個時候上界限定符的作用就體現(xiàn)出來了窿克,寫法是 List<? extends Animal>
,可以解釋為“可以放入任何 Animal 及其子類的列表”骏庸,之前講到泛型編譯后會抹除類型參數(shù)成 Object 類型,當(dāng)你使用上界限定符后年叮,就抹除成上界具被。
看代碼
public class GenericTest {
public static void main(String[] args) {
ArrayList<Integer> holder = new ArrayList<Integer>();
holder.add(new Integer(1));
// ArrayList<Number> numHolder = holder; // 1. compile failed. ArrayList<Number> 不是 ArrayList<Integer> 的父類
ArrayList<? extends Number> numHolder = holder; // 2. ok
Number num = numHolder.get(0); // 3. ok. return Number
numHolder.contains(new Integer(2)); // 4. ok
// numHolder.add(new Integer(2)); // 5. compile error
}
}
1 處編譯錯誤是因為泛型不是協(xié)變;Integer 是 Number 子類只损,故 2 處正確一姿;3 處正確,正如上段所說跃惫,上界限定符抹除成上屆叮叹,返回類型為 Number;4 處 ok爆存,是因為 contains 方法接受的是 Object 類蛉顽。5 處需要稍微注意,add
的形參類型也是 <? extends Number>
先较,編譯器只能知道類型是 Number 的子類携冤,并不能確定具體類型是什么,因此無法驗證類型的安全性闲勺。
下界限定符
下界限定符也稱為超類通配符曾棕,寫法是 List<? super Dog>
, 列表可以接受 Dog 類型及其父類對象。
上一接的第 5 處霉翔,如果向往里添加元素睁蕾,需要使用下界限定符。
public void writeTo(List<? super Integer> list) {
list.add(new Integer(2)); // ok
}
<? super Integer>
說明類型參數(shù)一定是 Integer 的父類。因此向里添加 Integer 類或其子類的對象一定是安全的子眶。
PECS 原則
根據(jù)上面的例子瀑凝,我們看到,使用上界限定符定義的類臭杰,可以向外提供東西粤咪,也就是說作為 Producer。使用下界限定符定義的類渴杆,可以作為 Consumer 接收外部往自身添加?xùn)|西寥枝。
總結(jié)起來就是, "Producer Extends, Consumer Super":
- "Producer Extends" - 如果你需要一個只讀類型磁奖,用它來produce T囊拜,那么使用
<? extends T>
- "Consumer Super" - 如果你需要一個只寫類型,用它來consume T比搭,那么使用
<? super T>
- 如果需要同時讀取以及寫入冠跷,那么我們就不能使用通配符了
下面一個方法同時涉及了這兩條規(guī)則。
public class Collections {
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i=0; i<src.size(); i++)
dest.set(i, src.get(i));
}
}