美文网首页
Java 泛型 PECS - 生产者extends消费者supe

Java 泛型 PECS - 生产者extends消费者supe

作者: zhengaoly | 来源:发表于2023-06-02 14:43 被阅读0次

    https://www.kancloud.cn/apachecn/howtodoinjava-zh/1952924

    昨天,我正在研究一些 Java 集合 API,并且发现了两种主要用于将元素添加到集合中的方法。 他们俩都使用泛型语法来获取方法参数。 但是,第一种方法是使用<? super T>,而第二种方法是使用<? extends E>。 为什么?

    首先,让我们看一下这两种方法的完整语法。

    此方法负责将集合c的所有成员添加到另一个调用此方法的集合中。

    boolean addAll(Collection<? extends E> c);

    调用此方法可将“元素”添加到集合c。

    public static <T> boolean addAll(Collection<? super T> c, T... elements);

    两者似乎都在做简单的事情,所以为什么它们都有不同的语法。 我们许多人可能会纳闷。 在这篇文章中,我试图揭开围绕它的概念的神秘性,该概念主要被称为 PECS (最早由 Joshua Bloch 在他的著作《Effective Java》中创造的术语)。

    为什么要使用泛型通配符?
    在我与 java 泛型 相关的上一篇文章中,我们了解到,泛型本质上用于类型安全性和不变式。 用例可以是 Integer 的列表,即List<Integer>。 如果您在 Java 中声明List<Integer>之类的列表,则 Java 保证它将检测并报告您将任何非整数类型插入上述列表的任何尝试。

    但是很多时候,我们面临这样的情况,为了特定的目的,我们必须在方法中传递类的子类型或超类型作为参数。 在这些情况下,我们必须使用协变(缩小引用)和逆变(扩大引用)之类的概念。

    了解<? extends T>
    这是 PECS 的第一部分,即 PE(生产者extends)。 为了将其与现实生活中的术语联系起来,让我们使用一篮子水果(即水果的集合)的类比。 当我们从篮子里摘水果时,我们要确保只取出水果而没有其他东西。 这样我们就可以编写如下泛型代码:

    Fruit get = fruits.get(0);
    在上述情况下,我们需要将水果的集合声明为List<? extends Fruit>。 例如:
    
    class Fruit {
       @Override
       public String toString() {
          return "I am a Fruit !!";
       }
    }
    
    class Apple extends Fruit {
       @Override
       public String toString() {
          return "I am an Apple !!";
       }
    }
    
    public class GenericsExamples
    {
       public static void main(String[] args)
       {
          //List of apples
          List<Apple> apples = new ArrayList<Apple>();
          apples.add(new Apple());
    
          //We can assign a list of apples to a basket of fruits;
          //because apple is subtype of fruit 
          List<? extends Fruit> basket = apples;
    
          //Here we know that in basket there is nothing but fruit only
          for (Fruit fruit : basket)
          {
             System.out.println(fruit);
          }
    
          //basket.add(new Apple()); //Compile time error
          //basket.add(new Fruit()); //Compile time error
       }
    }
    

    查看上面的for循环。 它确保了从篮子里出来的任何东西都肯定会结出果实; 因此,您可以遍历它,然后简单地将其浇铸成水果。 现在,在最后两行中,我尝试在购物篮中添加Apple和Fruit,但是编译器不允许我添加。 为什么?

    如果我们考虑一下,原因很简单。 <? extends Fruit>通配符告诉编译器我们正在处理水果类型的子类型,但是我们无法知道哪个水果,因为可能存在多个子类型。 由于没有办法说出来,而且我们需要保证类型安全(不变性),因此您不能在此类结构内放置任何内容。

    另一方面,由于我们知道它可能是Fruit的子类型,因此可以从结构中获取数据,并保证它是Fruit。

    在上面的示例中,我们从集合List<? extends Fruit> basket中取出元素。所以这个篮子实际上是在生产水果。 简而言之,当您只想从集合中检索元素时,请将其视为生产者并使用<? extends T>语法。 “生产者extends”现在对您来说更有意义。

    了解<? super T>
    现在以不同的方式查看上述用例。 假设我们正在定义一个方法,在此方法中,我们只会在此购物篮中添加不同的水果。 就像我们在帖子“ addAll(Collection<? super T> c, T... elements)”开头看到的方法一样。 在这种情况下,篮子用于存储元素,因此应称为元素的使用者。

    现在看下面的代码示例:

    class Fruit {
       @Override
       public String toString() {
          return "I am a Fruit !!";
       }
    }
    
    class Apple extends Fruit {
       @Override
       public String toString() {
          return "I am an Apple !!";
       }
    }
    
    class AsianApple extends Apple {
       @Override
       public String toString() {
          return "I am an AsianApple !!";
       }
    }
    
    public class GenericsExamples
    {
       public static void main(String[] args)
       {
          //List of apples
          List<Apple> apples = new ArrayList<Apple>();
          apples.add(new Apple());
    
          //We can assign a list of apples to a basket of apples
          List<? super Apple> basket = apples;
    
          basket.add(new Apple());      //Successful
          basket.add(new AsianApple()); //Successful
          basket.add(new Fruit());      //Compile time error
       }
    }
    

    我们可以在篮子内添加苹果,甚至是亚洲苹果,但不能在篮子中添加Fruit(苹果的超类型)。 为什么?

    原因是购物篮是对Apple的超类商品列表的引用。 同样,我们不知道它是是哪个超类型,但是我们知道可以将Apple及其任何子类型(它们是Fruit的子类型)添加为没有问题(您可以随时在超类的集合中添加一个子类)。 因此,现在我们可以在购物篮中添加任何类型的Apple。

    如何从这种类型的数据中获取数据呢? 事实证明,您唯一可以使用的是Object实例:由于我们无法知道它是哪个超类型,因此编译器只能保证它将是对Object的引用,因为Object是任何 Java 类型的超类型。

    在上面的示例中,我们将元素放入集合List<? super Apple> basket; 所以在这里,这个篮子实际上是在消耗苹果等元素。 简而言之,当您只想在集合中添加元素时,请将其视为使用者并使用<? super T>语法。 现在,“消费者super”对您也应该更有意义。

    总结
    基于上述推理和示例,让我们总结要点。

    如果需要从集合中检索类型T的对象,请使用<? extends T>通配符。
    如果需要将T类型的对象放入集合中,请使用<? super T>通配符。
    如果您需要同时满足这两个条件,请不要使用任何通配符。 就这么简单。
    简而言之,请记住术语 PECS。 生产者extends消费者super。 真的很容易记住。
    这就是 Java 中泛型中简单而又复杂的概念的全部。 通过评论让我知道您的想法。

    祝您学习愉快!

    相关文章

      网友评论

          本文标题:Java 泛型 PECS - 生产者extends消费者supe

          本文链接:https://www.haomeiwen.com/subject/bxxmedtx.html