第二十八條:利用有限通配符提升API的靈活性

一、實例(一)

我們有一個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();

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章