第二十八条:利用有限通配符提升API的灵活性
2016-09-05 11:29
316 查看
一、实例(一)
我们有一个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();
相关文章推荐
- 第二十八条:利用有限制通配符来提升API的灵活性
- 第28条 泛型——利用有限制通配符来提升API的灵活性
- Item 28 利用有限制通配符来提升API的灵活性
- 第28条:利用有限制通配符来提升API的灵活性
- effective java(28) 之利用有限制通配符来提升API的灵活性
- 利用有限制通配符来提升API的灵活性。
- 利用有限制通配符来提升API的灵活性
- EffectiveJava(28)怎么利用有限制的通配符类型来提升API的灵活性
- 第28条:利用有限制通配符来提升API的灵活性
- 利用有限通配符提供API灵活性
- (28):利用有限制通配符来提高API的灵活性
- Effective Java(数组和泛型的实现方式、用无限制的通配符提高API的灵活性)
- Effective Java(数组和泛型的实现方式、用无限制的通配符提高API的灵活性)
- 使用.NET Core搭建分布式音频效果处理服务(五)利用消息队列提升水平扩展灵活性
- Effective Java(数组和泛型的实现方式、用无限制的通配符提高API的灵活性)
- Effective Java(数组和泛型的实现方式、用无限制的通配符提高API的灵活性)
- Effective Java(数组和泛型的实现方式、用无限制的通配符提高API的灵活性)
- Effective Java(数组和泛型的实现方式、用无限制的通配符提高API的灵活性)
- Effective Java(数组和泛型的实现方式、用无限制的通配符提高API的灵活性)
- Effective Java(数组和泛型的实现方式、用无限制的通配符提高API的灵活性)