• 泛型-PECS原则


    PECS指“Producer Extends,Consumer Super”。换句话说,如果参数化类型表示一个生产者,就使用<? extends T>;如果它表示一个消费者,就使用<? super T>。

    不明白?

    先看看<? extends T>和<? super T>的区别。

    <? extends T><? super T>是Java泛型中的“通配符(Wildcards)”和“边界(Bounds)”的概念。

    • <? extends T>:是指 “上界通配符(Upper Bounds Wildcards)”
    • <? super T>:是指 “下界通配符(Lower Bounds Wildcards)”

    假设有这样一些类型定义

    //Lev 1
    
    class Food{}
    
    //Lev 2
    
    class Fruit extends Food{}
    
    class Meat extends Food{}
    
    //Lev 3
    
    class Apple extends Fruit{}
    
    class Banana extends Fruit{}
    
    class Pork extends Meat{}
    
    class Beef extends Meat{}
    
    //Lev 4
    
    class RedApple extends Apple{}
    
    class GreenApple extends Apple{}

    定义一个容器,Plate类。盘子里可以放一个泛型的“东西”:

    class Plate<T>{
    
    private T item;
    
    public Plate(T t){item=t;}
    
    public void set(T t){item=t;}
    
    public T get(){return item;}
    
    }

    现在我定义一个“水果盘子”,逻辑上水果盘子当然可以装苹果。

    Plate<Fruit> p=new Plate<Apple>(new Apple());

    但实际上Java编译器不允许这个操作。会报错,“装苹果的盘子”无法转换成“装水果的盘子”。

     其实很好解释:

    • 苹果 IS-A 水果
    • 装苹果的盘子 NOT-IS-A 装水果的盘子

    所以,就算容器里装的东西之间有继承关系,但容器之间是没有继承关系的。于是有了<? extends T><? super T>。

    上界通配符 Plate<? extends Fruit> 覆盖下图中蓝色的区域。

    下界通配符 Plate<? super Fruit> 覆盖下图中红色的区域。

     

    接下来看一下PECS,再举个例子:

    下面是一个简单的Stack的API接口:

    public class  Stack<E>{
        public Stack();
        public void push(E e):
        public E pop();
        public boolean isEmpty();
        //按顺序将一系列元素全部放入Stack中,你可能想到的实现方式如下:
        public void pushAll(Iterable<E> src){
            for(E e : src)
                push(e)
        }
    }

    这时,有个Stack<Number>,想要灵活的处理Integer,Long等Number的子类型的集合:

    Stack<Number> numberStack = new Stack<Number>();
    Iterable<Integer> integers = ....;
    numberStack.pushAll(integers);

    此时代码编译无法通过,因为对于类型Number和Integer来说,虽然后者是Number的子类,但是对于任意Number集合(如List<Number>)不是Integer集合(如List<Integer>)的超类,因为泛型是不可变的。

    幸好java提供了一种叫有限通配符的参数化类型,pushAll参数替换为“E的某个子类型的Iterable接口”:

    public void pushAll(Iterable<? extends E> src){
        for (E e: src)
            push(e);
    }

    这样就可以正确编译了,这里的<? extends E>就是所谓的 producer-extends。这里的Iterable就是生产者,要使用<? extends E>。因为Iterable<? extends E>可以容纳任何E的子类。在执行操作时,可迭代对象的每个元素都可以当作是E来操作。

    与之对应的是:假设有一个方法popAll()方法,从Stack集合中弹出每个元素,添加到指定集合中去:

    public void popAll(Collection<E> dst){
           if(!isEmpty()){
                    dst.add(pop());
            }
    }

    假设有一个Stack<Number>和Collection<Object>对象:

    Stack<Number> numberStack = new Stack<Number>();
    Collection<Object> objects = ...;
    numberStack.popAll(objects);

    同样上面这段代码也无法通过,解决的办法就是使用Collection<? super E>。这里的objects是消费者,因为是添加元素到objects集合中去。使用Collection<? super E>后,无论objects是什么类型的集合,满足一点的是他是E的超类,所以不管这个参数化类型具体是什么类型都能将E装进objects集合中去。

    btw,我们反过来呢?以下两种均不能通过编译。

    public void pushAll(Iterable<? super E> src){
        for (E e: src)
            push(e);
    }

    因为下界规定了元素的最小粒度的下限,实际上是放松了容器元素的类型控制。既然元素是E的基类,那往里存粒度比E小的都可以。但往外读取元素就费劲了,只有所有类的基类Object对象才能装下。但这样的话,元素的类型信息就全部丢失。Stack自然也无法存E的基类。

    public void popAll(Collection<? extends E> dst){
           if(!isEmpty()){
                    dst.add(pop());
            }
    }

    原因是编译器只知道容器内是Fruit或者它的派生类,但具体是什么类型不知道。编译器在?标上一个占位符:CAP#1,来表示捕获一个E或E的子类,具体是什么类不知道,代号CAP#1。然后无论是想往里插入A或者B或者C编译器都不知道能不能和这个CAP#1匹配,所以就都不允许。

    This means that when a parameterized type being passed to a method will produce instances of T (they will be retrieved from it in some way), ? extends T should be used, since any instance of a subclass of T is also a T.

    When a parameterized type being passed to a method will consume instances of T (they will be passed to it to do something), ? super T should be used because an instance of T can legally be passed to any method that accepts some supertype of T. A Comparator<Number> could be used on a Collection<Integer>, for example. ? extends T would not work, because a Comparator<Integer> could not operate on a Collection<Number>.

    --from stackOverFlow https://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super

    另附阿里java开发手册第一章第五节第6条

    6.【强制】泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用add方 法,而<? super T>不能使用get方法,作为接口调用赋值时易出错。 说明:扩展说一下PECS(Producer Extends Consumer Super)原则:第一、频繁往外读取内 容的,适合用<? extends T>。第二、经常往里插入的,适合用<? super T>。 

    参考:https://itimetraveler.github.io/2016/12/27/%E3%80%90Java%E3%80%91%E6%B3%9B%E5%9E%8B%E4%B8%AD%20extends%20%E5%92%8C%20super%20%E7%9A%84%E5%8C%BA%E5%88%AB%EF%BC%9F/

    https://blog.csdn.net/rj08zhou/article/details/45063451

  • 相关阅读:
    ping和telnet
    nginx下No input file specified错误的解决
    【Git】删除某个全局配置项
    windows7使用Sphinx+PHP+MySQL详细介绍
    TortoiseGit需要重复填写用户名和密码的问题
    【算法】字符串数组的排序时间复杂度问题
    java随机生成6位随机数 5位随机数 4位随机数
    Linux下MySQL报Table 'xxx' doesn't exist错误解决方法,表名存在大小写区分
    Cannot find ./catalina.sh The file is absent or does not have execute permission This file is nee
    Linux 服务器安装jdk,mysql,tomcat简要教程
  • 原文地址:https://www.cnblogs.com/steve-jiang/p/12172273.html
Copyright © 2020-2023  润新知