有时候,我们需要的灵活性要比不可变类型所能提供的更多.所以针对一些通用性的方法,通常用泛型代替固定的数据类型,特别是当你要将某个方法打包成Jar的时候. 结合之前的例子,我们增加尝试用有限制的通配符类型来加大方法的灵活性
public class Stack<E> { public void pushAll(Iterable<? entends E> src){ for(E e:src){ //push为一个将元素加入到数组中的方法 push(e); } } }这个方法按顺序将一系列的元素全部放到堆栈中. 如果我们将? entends E 替换成 E ,虽然它能通过编译,但是如果一个Stack调用了push(Integer),控制台将会报错. 但是用我们写的这个方法,不仅Stack可以编译通过,没有通过初始的pushAll声明进行编译的客户端代码一样可以.
那如果我们想从堆栈中弹出元素,并将元素添加到指定集合中,该怎么做防止它报错呢? 我们应该这么做 —>>>
public void popAll(Collection<? super E> dst){ //isEmpty是一个判断数组是否为空的方法 while(!isEmpty()){ //pop()移出元素方法 dst.add(pop()); } }为什么pushAll()我们用extends,下面的例子却用super呢? 这是因为pushAll()中,我们是加入元素,pop是移除元素.所以,为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符类型,如果参数化类型表示一个T生产者,就使用
static <E> e reduce(List<E> list,Function<E> f,E initVal); ----->>>> static <E> E reduce(List<? extends E> list,Function<E> f,E initVal);如果有一个List 想通过Function对它进行简化,上面那个不能通过编译,下面的就可以
当然,通配符类型并不是万能的,他不能作为返回类型,而且它除了为用户提供额外的灵活性之外,他还会强制用户在客户端代码中使用通配符类型. 成熟的使用通配符类型对于类的用户应该是几乎无形的,.它们是方法能够接受他们应该接受的参数,拒绝那些应该拒绝的参数.如果类的用户必须考虑通配符类型,类的API或许就会出错.我们可以通过一个不怎么优雅的方式解决这个问题 ——->>>>通过一个显式的类型参数来告诉他要使用哪种类型 如:
Set<Interger> integers = ...; Set<Double> doubles = ...; Set<Number> numbers = union(integers,doubles); ---->>> Set<Number> numbers = Union.<Number>union(integers,doubles);下面,我们通过一个方法解读一下有限制的通配符的使用
//comparable始终是消费者 public static <T extends Comparable<? super T>> T max(List<? extends T> list){ //Iterator i = list.iterator(); //list不是一个List<T> 所以他的iterator方法没有返回Iterator<T> //他返回的是某个子类型的iterator Iterator<? extends T> i = list.iterator(); T result = i.next(); while(i.hasNext()){ T t = i.next(); if(t.compareTo(result)>0){ result = t; } return result; } }类型参数和通配符之间具有双重性,许多方法可以利用其中一个或者另一个进行声明.那么,它们有什么区别呢? —->>>>
//无限制类型参数 public static <E> void swap(List<E> list,int i,int j); //无限制通配符 public static void swap(List<?> list,int i,int j); 在公共API中,推荐使用第二种,因为它更简单.如果类型参数只在方法声明中出现一次,就可以用通配符取代他,取代类型取决于其本身类型. 如果使用第二种方法,还需要注意,编写一个私有的辅助方法来捕捉通配符类型,因为List<?>不能存放null之外的任何值 --->>> public static void swap(List<?> list,int i,int j){ swapHelper(list,i,j); } //swapHelper知道list是什么类型 private static <E> void swapHelper(List<E> list,int i,int j){ list.set(i,list.set(j,list.get(i))); } 总结:在API中使用通配符类型比较需要技巧,也会使API灵活的多.如果编写的是将被广泛实用的类库,则一定要适当地利用通配符.记住基本的原则:producer-extends,consumer-super(PECS).当然,所有的comparable和comparator都是消费者