💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 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 中泛型中简单而又复杂的概念的全部。 通过评论让我知道您的想法。 **祝您学习愉快!**