有限制的通配符類型
修改前:
public void pushAll(Iterable<E> src) {
for (E e : src) {
push(e)
}
}
修改後:
public void pushAll(Iterable<? extends E> src) {
for (E e : src) {
push(e)
}
}
pushAll的輸入參數類型不應該爲“E的Iterable接口”,而應該爲“E的某個子類型的Iterable接口”,有一個通配符類型正符全此意:Iterable<? Extends E>。(確定了子類型後,每個類型都是自身的子類型,即便它沒有將自身擴展。)
爲了獲得最大限度的靈活性,要在表示生產者和消費者的輸入參數上使用通配符類型。如果某個輸入參數即是生產者,又是消費都,那麼通配符類型對你就沒有什麼好處了,因爲你需要嚴格的類型匹配,這是不用任何通配符而得到的。
下面的助記符便於讓你記住要使用哪種通配符類型:
PECS 表示:producer-extends, consumer-super
換句話說,如果參數化類型表示一個T生產者,就使用<? extends T>;如果它表示一個T消費者,就使用<? super T>。在我們的Stack示例中,pushAll的src參數產生E實例供Stack使用,因此src相應的類型爲Iterable<? extends E>;popAll的dst參數通過Stack消費E實例,因此dst相應的類型爲Collection<? super E>。PECS這個助記符突出了使用通配符的基本原則。Naftalin和Wadler稱之爲Get and Put Principle
類型參數和通配符之間具有雙重性,許多方法都可以利用其中一個或者另一個進行聲明。例如,下面是可能的兩種靜態方法聲明,來交換列表中的兩個被索引的項目。第一個使用無限制的類型參數,第二個使用無限制的通配符
public static <E> void swap(List<E> list, int i, int j);
public static void swap(List<?> list, int i , int j);
在公共API中,第二種更好一些,因爲它更簡單。將它傳到一個列表中--------任何列表----------方法就會交換被索引的元素。不用擔心類型參數。一般來說,如果類型參數只在方法聲明中出現一次,就可以用通配符取代它。如果是無限制的類型參數,就用無限制的通配符取代它;如果是有限制的類型參數,就用有限制的通配符取代它
總而言之,在API中使用通配符類型雖然比較需要技巧,但是使API變得靈活得多。如果編寫的是將被廣泛使用的類庫,則一定要適當地利用通配符。記住基本的原則:producer-extends, consumer-super(PECS)還要記住所有的comparable和comparator都是消費者。
摘抄自:Effective Java