# Java 泛型 PECS - 生产者`extends`消费者`super`

> 原文： [https://howtodoinjava.com/java/generics/java-generics-what-is-pecs-producer-extends-consumer-super/](https://howtodoinjava.com/java/generics/java-generics-what-is-pecs-producer-extends-consumer-super/)

昨天，我正在研究一些 [**Java 集合**](//howtodoinjava.com/java/collections/useful-java-collection-interview-questions/ "Useful java collection interview questions") API，并且发现了两种主要用于将元素添加到集合中的方法。 他们俩都使用泛型语法来获取方法参数。 但是，第一种方法是使用`<? super T>`，而第二种方法是使用`<? extends E>`。 为什么？

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

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

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

```

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

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

```

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

## 为什么要使用泛型通配符？

在我与 [**java 泛型**](//howtodoinjava.com/java/generics/complete-java-generics-tutorial/ "Complete Java Generics Tutorial") 相关的上一篇文章中，我们了解到，泛型本质上用于**类型安全性和不变式**。 用例可以是 Integer 的列表，即`List<Integer>`。 如果您在 Java 中声明`List<Integer>`之类的列表，则 Java 保证它将检测并报告您将任何非整数类型插入上述列表的任何尝试。

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

## 了解`<? extends T>`

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

```java
Fruit get = fruits.get(0);
```

在上述情况下，我们需要将水果的集合声明为`List<? extends Fruit>`。 例如：

```java
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)`”开头看到的方法一样。 在这种情况下，篮子用于存储元素，因此应称为元素的使用者。

现在看下面的代码示例：

```java
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`**”对您也应该更有意义。

## 总结

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

1.  如果需要从集合中检索类型`T`的对象，请使用`<? extends T>`通配符。
2.  如果需要将`T`类型的对象放入集合中，请使用`<? super T>`通配符。
3.  如果您需要同时满足这两个条件，请不要使用任何通配符。 就这么简单。
4.  简而言之，请记住术语 **PECS**。 生产者`extends`消费者`super`。 真的很容易记住。

这就是 Java 中泛型中简单而又复杂的概念的全部。 通过评论让我知道您的想法。

**祝您学习愉快！**