一、實例(一)
我們有一個Stack類
public Stack<E>{
//有如下方法
public void put(E data);
public E pop();
}
我們想增加能夠將容器中的所有數據存儲到棧中的方法:putAll(Iterator<E> iterator)
Stack類的完整實現:
public class GenericsStack<E> {
private static final int DEFAULT_SIZE = 16;
//該行出現了錯誤,無法創建泛型數組
private Object[] objects = new Object[DEFAULT_SIZE];
private int count = 0;
public void put(E obj){
expandArray();
objects[count] = obj;
++count;
}
private void expandArray(){
if(count == objects.length){
//擴充容量
objects = Arrays.copyOf(objects, 2*count);
}
}
public E pop(){
//首先判斷是否數組爲空
if (!isEmpty()){
@SuppressWarnings("unchecked")
E obj = (E)objects[count];
--count;
return obj;
}
return null;
}
private boolean isEmpty(){
if (count == 0){
return true;
}
else {
return false;
}
}
//獲取迭代器
public void putAll(Iterator<E> iterator){
while(iterator.hasNext()){
E e = iterator.next();
put(e);
}
}
}
使用:
List<Number> numbers = new ArrayList<Number>();
GenericsStack<Number> stack = new GenericsStack<Number>();
stack.putAll(numbers.iterator());
既然是Number的棧,那麼應該可以存儲Number的子類,Integer纔是。沒錯使用Stack的put(E data)方法確實可以。
那麼表示putAll(Iterator<E> iterator)應該也能接收List<Integer>的迭代器然後遍歷Integer纔是,因爲Stack能夠存儲Integer。
但是由於泛型的不可變性,Iterator<Number> != Iterator<Integer>,也就是說
List<Integer> numbers = new ArrayList<Integer>();
GenericsStack<Number> stack = new GenericsStack<Number>();
stack.putAll(numbers.iterator());//存入了Iterator<Integer>,是會報錯的
是錯誤的。那麼顯然這個方法是不完整的。
那麼怎麼能夠讓putAll(Iterator<E> iterator)接收Iterator<Integer>呢?
將輸入參數中的Iterator<E> 修改成 Iterator<? extends E> 表示:接收的參數擴展爲E及其的子類。
public void putAll(Iterator<? extends E> iterator)
因爲Integer是Number的子類,所以成立,這就不會報錯了。
二、實例(二)
既然我們有了讓容器的數據,全部放到Stack中的方法,那麼應該還有一個能夠讓Stack類內的數據全部放到容器中的方法。
public void popAll(Collection<E> collection){
E data = pop();
if (data != null){
collection.add(data);
}
}
然後我們來調用這個方法 //使用正確
Collection<Number> collection = new ArrayList<Number>();
stack.popAll(collection);
//使用錯誤
Collection<Object> collection2 = new ArrayList<Object>();
stack.popAll(collection2);
講道理:Number的父類是Object,那麼Number實例是能夠存放到Collection<Object>這個容器中的。
所以我們需要改進。既然Object是Number的父類,我們將輸入參數Collection<E> 改成 Collection<? super E>,這樣就沒問題了。
public void popAll(Collection<? super E> collection)
表示接受:爲E的父類的容器。
三、如何判斷何時使用<? extends E> 何時使用 <? super E>
當參數化類型(?)表示E的生產者的時候,用<? extends E>
比如:上述例子中,putAll()的iterator參數,產生E的實例到類中,供Stack使用
當參數化類型(?)表示E的消費者的時候,用<? super E>
比如:popAll()中的collection參數,將Stack中E的實例裝入到Collection中
舉個複雜的例子:我們之前寫過一個將容器中的數據相減的方法
public <T> T reduce(List<T> list,Function<T> function,T initalVal){
Iterator<T> iterator = list.iterator();
T result = initalVal;
//相減
while (iterator.hasNext()){
result = function.apply(result,iterator.next());
}
return result;
}
這個方法中,List<T> list 參數應該是作爲該方法中的生產者,所以應該變成List<? extends T> list
Function<T> 該參數表示減法的執行類,那麼就應該是消費者,變成Function<? super T> function
但是如果該方法的功能不是減法,而是Function方法的輔助類的話就不一樣了,意思就是,該方法的功能是根據Function來確定的。那麼就不能夠判斷Function是消費者還是生產者了。這個時候由於不能判斷,那麼就應該保持不變。還是Function<T> function。
四、利用有限通配符改進方法
改進第二十七條的union方法,我們先看一下原先的方法
//先看下原先的泛型方法
public <E> Set<E> union(Set<E> set1,Set<E> set2){
Set<E> set = new HashSet<E>(set1);
set.addAll(set2);
return set;
}
然後我們發現,兩個Set輸入參數,其實都是生產者,也就是都是添加E到該類中,那麼就應該改進成
public <E> Set<E> union(Set<? extends E> set1,Set<? extends E> set2)
使用:
public static void main(String[] args){
Set<Integer> intSet = new HashSet<Integer>();
Set<Double> doubleSet = new HashSet<Double>();
Set<Number> numberSet = unionSet(intSet, doubleSet);//報錯
}
額,爲什麼會報錯呢?
因爲虛擬機的參數推斷機制特別複雜,我們這樣子寫虛擬機無法推斷出E到底是哪個類。根據我們之前學習的類型推斷,虛擬機應該是根據Set<? extends E> set1,來推斷出E的類型,假如?是Integer類型,那麼E是什麼類型呢。。。只能知道E是Integer的父類而已。所以說,這樣子寫的話,E其實還是未知的類型,所以會報錯。
那麼如何解決這個問題呢?
Java提供了顯示的類型參數推斷機制
Set<Number> numberSet = Union.<Number> unionSet(intSet,doubleSet);
告訴該方法,E爲Number。非靜態方法用this代替Union。
改進計算容器的最大值的方法(calculateMax())
先看一下原先寫的代碼:
public <T extends Comparable<T>> T calculateMax(List<T> list){
Iterator<T> iterator = list.iterator();
T result = iterator.next();
while (iterator.hasNext()){
T t = iterator.next();
//需要判斷大小
if (result.compareTo(t) < 0){
result = t;
}
}
return result;
}
我們要做什麼改進呢?
①、首先輸入參數List<T> list 是生產者。生產T實例,那麼就應該變成List<? extends T> list
②、Comparable<T>是消費者。因爲它獲取T,並將T排序。那麼就應該變成Comparable<? super T>
(理解什麼是生產者,什麼是消費者是至關重要的)
所以最終結果就是:
public <T extends Comparable<? super T>> T calculateMax(List<? extends T> list){
Iterator<T> iterator = list.iterator();//該行報錯了
//...以下省略
return result;
}
原因:list.iterator()的泛型是<? extends T> 是T的子類 而 被賦值參數的類型是 Iterator<T> 我們知道泛型是不可具體化的,所以無法互相轉換。解決辦法
Iterator<? extends T> iteraot = list.iterator();