zoukankan      html  css  js  c++  java
  • EffectiveJava(28)怎么利用有限制的通配符类型来提升API的灵活性

    有时候,我们需要的灵活性要比不可变类型所能提供的更多.所以针对一些通用性的方法,通常用泛型代替固定的数据类型,特别是当你要将某个方法打包成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都是消费者
    
  • 相关阅读:
    高级映射之事务
    配置tomcat-users.xml文件
    动态SQL之标签
    性能测试
    Service
    添加 aar 或 jar 包依赖 的方式
    安卓设备 以太网代理 问题排查
    剑指offer:面试题15、链表中倒数第 K 个结点
    剑指offer:面试题14、调整数组顺序使奇数位于偶数前面
    剑指offer:面试题13、在O(1)时间删除链表结点
  • 原文地址:https://www.cnblogs.com/qwop/p/6637279.html
Copyright © 2011-2022 走看看