這篇文章談一談Java泛型聲明<? extends E>和<? super E>的作用和區別
<? extends E>
<? extends E> 是 Upper Bound(上限) 的通配符,用來限制元素的類型的上限,比如
- List<? extends Fruit> fruits;
表示集合中的元素類型上限爲Fruit類型,即只能是Fruit或者Fruit的子類,因此對於下面的賦值是合理的
- fruits = new ArrayList<Fruit>();
- fruits = new ArrayList<Apple>();
如果集合中元素類型爲Fruit的父類則會編譯出錯,比如
fruits = new ArrayList<Object>();//編譯不通過
有了上面的基礎,接着來看看 <? extends E>限定的集合的讀寫操作
1、寫入
因爲集合fruits中裝的元素類型爲Fruit或Fruit子類,直覺告訴我們,往fruits中添加一個Fruit類型對象或其子類對象是可行的
- fruits.add(new Fruit());//編譯不通過
- fruits.add(new Apple());//編譯不通過
結果是編譯都不通過,爲什麼?因爲<? extends Fruit>只是告訴編譯器集合中元素的類型上限,但它具體是什麼類型編譯器是不知道的,fruits可以指向ArrayList<Fruit>,也可以指向ArrayList<Apple>、ArrayList<Banana>,也就是說它的類型是不確定的,既然是不確定的,爲了類型安全,編譯器只能阻止添加元素了。舉個例子,當你添加一個Apple時,但fruits此時指向ArrayList<Banana>,顯然類型就不兼容了。當然null除外,因爲它可以表示任何類型。
2、讀取
無論fruits指向什麼,編譯器都可以確定獲取的元素是Fruit類型,所有讀取集合中的元素是允許的
Fruit fruit = fruits.get(0);//編譯通過
補充:<?>是<? extends Object>的簡寫
<? super E>
<? super E> 是 Lower Bound(下限) 的通配符 ,用來限制元素的類型下限,比如
List<? super Apple> apples;
表示集合中元素類型下限爲Apple類型,即只能是Apple或Apple的父類,因此對於下面的賦值是合理的
apples = new ArrayList<Apple>();
- apples = new ArrayList<Fruit>();
- apples = new ArrayList<Object>();
如果元素類型爲Apple的子類,則編譯不同過
apples = new ArrayList<RedApple>();//編譯不通過
同樣看看<? super E>限定的集合的讀寫操作
1、寫入
因爲apples中裝的元素是Apple或Apple的某個父類,我們無法確定是哪個具體類型,但是可以確定的是Apple和Apple的子類是和這個“不確定的類”兼容的,因爲它肯定是這個“不確定類型”的子類,也就是說我們可以往集合中添加Apple或者Apple子類的對象,所以對於下面的添加是允許的
apples.add(new Apple());
- apples.add(new RedApple());
它無法添加Fruit的任何父類對象,舉個例子,當你往apples中添加一個Fruit類型對象時,但此時apples指向ArrayList<Apple>,顯然類型就不兼容了,Fruit不是Apple的子類
apples.add(new Fruit());//編譯不通過
2、讀取
編譯器允許從apples中獲取元素的,但是無法確定的獲取的元素具體是什麼類型,只能確定一定是Object類型的子類,因此我們想獲得存儲進去的對應類型的元素就只能進行強制類型轉換了
Apple apple = (Apple)apples.get(0);//獲取的元素爲Object類型
問題來了,JDK1.5引入泛型的目的是爲了避免強制類型轉換的繁瑣操作,那麼使用泛型<? super E>幹嘛呢?這裏就得談到泛型PECS法則了
PECS法則
PECS法則:生產者(Producer)使用extends,消費者(Consumer)使用super
1、生產者
如果你需要一個提供E類型元素的集合,使用泛型通配符<? extends E>。它好比一個生產者,可以提供數據。
2、消費者
如果你需要一個只能裝入E類型元素的集合,使用泛型通配符<? super E>。它好比一個消費者,可以消費你提供的數據。
3、既是生產者也是消費者
既要存儲又要讀取,那就別使用泛型通配符。
PECS例子
JDK集合操作幫助類Collections中的例子
* Copies all of the elements from one list into another. After the * operation, the index of each copied element in the destination list * will be identical to its index in the source list. The destination * list must be at least as long as the source list. If it is longer, the * remaining elements in the destination list are unaffected. <p> * * This method runs in linear time. * * @param dest The destination list. * @param src The source list. * @throws IndexOutOfBoundsException if the destination list is too small * to contain the entire source List. * @throws UnsupportedOperationException if the destination list's * list-iterator does not support the <tt>set</tt> operation. */ public static <T> void copy(List<? super T> dest, List<? extends T> src) { int srcSize = src.size(); if (srcSize > dest.size()) throw new IndexOutOfBoundsException("Source does not fit in dest"); if (srcSize < COPY_THRESHOLD || (src instanceof RandomAccess && dest instanceof RandomAccess)) { for (int i=0; i<srcSize; i++) dest.set(i, src.get(i)); } else { ListIterator<? super T> di=dest.listIterator(); ListIterator<? extends T> si=src.listIterator(); for (int i=0; i<srcSize; i++) { di.next(); di.set(si.next()); } } }
總結
爲什麼要引入泛型通配符?一句話:爲了保證類型安全。
具體請看 《effective java》裏,Joshua Bloch提出的PECS原則:
- 頻繁往外讀取內容的,適合用上界Extends。
- 經常往裏插入的,適合用下界Super。