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

来源:互联网 发布:rational rose是否mac 编辑:程序博客网 时间:2024/05/16 18:32

一、实例(一)

我们有一个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();

0 0
原创粉丝点击