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

一、实例(一)

我们有一个Stack类

public Stack{
    //有如下方法
    public void put(E data);
    public E pop();
}
我们想增加能够将容器中的所有数据存储到栈中的方法: putAll(Iterator  iterator)

Stack类的完整实现:

public class GenericsStack {
	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 iterator){
		while(iterator.hasNext()){
			E e = iterator.next();
			put(e);
		}
	}
}
使用:

		List numbers = new ArrayList(); 
		GenericsStack stack = new GenericsStack();
		stack.putAll(numbers.iterator());
既然是Number的栈,那么应该可以存储Number的子类,Integer才是。没错使用Stack的put(E data)方法确实可以。

那么表示putAll(Iterator iterator)应该也能接收List的迭代器然后遍历Integer才是,因为Stack能够存储Integer。

但是由于泛型的不可变性,Iterator != Iterator,也就是说

List numbers = new ArrayList(); 
GenericsStack stack = new GenericsStack();
stack.putAll(numbers.iterator());//存入了Iterator,是会报错的
是错误的。那么显然这个方法是不完整的。

那么怎么能够让putAll(Iterator iterator)接收Iterator呢?

将输入参数中的Iterator 修改成 Iterator 表示:接收的参数扩展为E及其的子类。

public void putAll(Iterator iterator)

因为Integer是Number的子类,所以成立,这就不会报错了。

二、实例(二)

既然我们有了让容器的数据,全部放到Stack中的方法,那么应该还有一个能够让Stack类内的数据全部放到容器中的方法。

	public void popAll(Collection collection){
		E data = pop();
		if (data != null){
			collection.add(data);
		}
	}
然后我们来调用这个方法
		//使用正确
		Collection collection = new ArrayList();
		stack.popAll(collection);
		//使用错误
		Collection collection2 = new ArrayList();
		stack.popAll(collection2);讲道理:Number的父类是Object,那么Number实例是能够存放到Collection这个容器中的。 
  

所以我们需要改进。既然Object是Number的父类,我们将输入参数Collection 改成 Collection,这样就没问题了。

public void popAll(Collection collection)

表示接受:为E的父类的容器。

三、如何判断何时使用 何时使用

当参数化类型(?)表示E的生产者的时候,用

比如:上述例子中,putAll()的iterator参数,产生E的实例到类中,供Stack使用

当参数化类型(?)表示E的消费者的时候,用

比如:popAll()中的collection参数,将Stack中E的实例装入到Collection中


举个复杂的例子:我们之前写过一个将容器中的数据相减的方法

	public  T reduce(List list,Function function,T initalVal){
		Iterator iterator = list.iterator();
		T result = initalVal;
		//相减
		while (iterator.hasNext()){
			result = function.apply(result,iterator.next());
		}
		return result;
	}
这个方法中,List list 参数应该是作为该方法中的生产者,所以应该变成List list

Function 该参数表示减法的执行类,那么就应该是消费者,变成Function function

但是如果该方法的功能不是减法,而是Function方法的辅助类的话就不一样了,意思就是,该方法的功能是根据Function来确定的。那么就不能够判断Function是消费者还是生产者了。这个时候由于不能判断,那么就应该保持不变。还是Function function。 


四、利用有限通配符改进方法

改进第二十七条的union方法,我们先看一下原先的方法

//先看下原先的泛型方法
public  Set union(Set set1,Set set2){
    Set set  = new HashSet(set1);
    set.addAll(set2);
    return set;
}
然后我们发现,两个Set输入参数,其实都是生产者,也就是都是添加E到该类中,那么就应该改进成

public Set union(Set set1,Set set2)

使用:

	public static void main(String[] args){
		Set intSet = new HashSet();
		Set doubleSet = new HashSet();
		Set numberSet = unionSet(intSet, doubleSet);//报错
	}
额,为什么会报错呢?

因为虚拟机的参数推断机制特别复杂,我们这样子写虚拟机无法推断出E到底是哪个类。根据我们之前学习的类型推断,虚拟机应该是根据Set set1,来推断出E的类型,假如?是Integer类型,那么E是什么类型呢。。。只能知道E是Integer的父类而已。所以说,这样子写的话,E其实还是未知的类型,所以会报错。

那么如何解决这个问题呢?

Java提供了显示的类型参数推断机制

Set numberSet = Union. unionSet(intSet,doubleSet);

告诉该方法,E为Number。非静态方法用this代替Union。


改进计算容器的最大值的方法(calculateMax())

先看一下原先写的代码:

public > T calculateMax(List list){
		Iterator iterator = list.iterator();
		T result = iterator.next();
		while (iterator.hasNext()){
			T t = iterator.next();
			//需要判断大小
			if (result.compareTo(t) < 0){
				result = t;
			}
		}
		return result;
	}
我们要做什么改进呢?

①、首先输入参数List list 是生产者。生产T实例,那么就应该变成List list

②、Comparable是消费者。因为它获取T,并将T排序。那么就应该变成Comparable

(理解什么是生产者,什么是消费者是至关重要的)

所以最终结果就是:

	public > T calculateMax(List list){
		Iterator iterator = list.iterator();//该行报错了
		//...以下省略
		return result;
	}
原因:list.iterator()的泛型是 是T的子类 而 被赋值参数的类型是 Iterator 我们知道泛型是不可具体化的,所以无法互相转换。解决办法

Iterator iteraot = list.iterator();

你可能感兴趣的:(EffectiveJava)