Java 什么是 PECS(生产者扩展消费者超级)?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2723397/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 12:07:44  来源:igfitidea点击:

What is PECS (Producer Extends Consumer Super)?

javagenericssuperbounded-wildcardpecs

提问by peakit

I came across PECS (short for Producer extendsand Consumer super) while reading up on generics.

我在阅读泛型时遇到了 PECS(Producerextends和 Consumer 的super缩写)。

Can someone explain to me how to use PECS to resolve confusion between extendsand super?

有人可以向我解释如何使用 PECS 解决extends和之间的混淆super吗?

采纳答案by Michael Myers

tl;dr:"PECS" is from the collection's point of view. If you are onlypulling items from a generic collection, it is a producer and you should use extends; if you are onlystuffing items in, it is a consumer and you should use super. If you do both with the same collection, you shouldn't use either extendsor super.

tl;dr:“PECS”是从收藏的角度来看的。如果您只是从通用集合中提取项目,则它是一个生产者,您应该使用extends; 如果你只是把物品塞进去,它是一个消费者,你应该使用super. 如果您使用相同的集合同时执行这两个操作,则不应使用extendssuper



Suppose you have a method that takes as its parameter a collection of things, but you want it to be more flexible than just accepting a Collection<Thing>.

假设您有一个方法将一组事物作为其参数,但您希望它比仅接受Collection<Thing>.

Case 1: You want to go through the collection and do things with each item.
Then the list is a producer, so you should use a Collection<? extends Thing>.

案例 1:您想浏览集合并对每个项目进行处理。
那么列表是一个生产者,所以你应该使用一个Collection<? extends Thing>.

The reasoning is that a Collection<? extends Thing>could hold any subtype of Thing, and thus each element will behave as a Thingwhen you perform your operation. (You actually cannot add anything to a Collection<? extends Thing>, because you cannot know at runtime which specificsubtype of Thingthe collection holds.)

原因是 aCollection<? extends Thing>可以包含 的任何子类型Thing,因此Thing当您执行操作时,每个元素都将表现为 a 。(您实际上无法向 a 添加任何内容Collection<? extends Thing>,因为您无法在运行时知道集合的哪个特定子类型Thing。)

Case 2: You want to add things to the collection.
Then the list is a consumer, so you should use a Collection<? super Thing>.

案例 2:您想向集合中添加内容。
那么列表是一个消费者,所以你应该使用一个Collection<? super Thing>.

The reasoning here is that unlike Collection<? extends Thing>, Collection<? super Thing>can always hold a Thingno matter what the actual parameterized type is. Here you don't care what is already in the list as long as it will allow a Thingto be added; this is what ? super Thingguarantees.

这里的推理是,无论实际参数化类型是什么Collection<? extends Thing>Collection<? super Thing>都可以始终保持 a不同Thing。在这里,您不关心列表中已有的内容,只要它允许Thing添加a即可;这就是? super Thing保证。

回答by ColinD

As I explain in my answerto another question, PECS is a mnemonic device created by Josh Bloch to help remember Producer extends, Consumer super.

正如我在对另一个问题的回答中所解释的那样,PECS 是一种由 Josh Bloch 创建的助记装置,用于帮助记住Producer extends, C消费者super

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

When a parameterized type being passed to a method will consumeinstances of T(they will be passed to it to do something), ? super Tshould be used because an instance of Tcan 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 Twould not work, because a Comparator<Integer>could not operate on a Collection<Number>.

这意味着当传递给方法的参数化类型将产生 的实例T(它们将以某种方式从中检索)时,? extends T应该使用,因为 的子类的任何实例T也是T.

当一个参数化类型被传递给方法会消耗的情况下,T(他们将被传递给它做一些事情),? super T应该使用,因为实例T可以合法地传递给一个接受的某些超类型的任何方法T。例如,AComparator<Number>可以用于 a Collection<Integer>? extends T不会工作,因为 aComparator<Integer>无法对 a 进行操作Collection<Number>

Note that generally you should only be using ? extends Tand ? super Tfor the parameters of some method. Methods should just use Tas the type parameter on a generic return type.

请注意,通常您应该只使用? extends T? super T用于某些方法的参数。方法应该只T用作泛型返回类型的类型参数。

回答by Gab

public class Test {

    public class A {}

    public class B extends A {}

    public class C extends B {}

    public void testCoVariance(List<? extends B> myBlist) {
        B b = new B();
        C c = new C();
        myBlist.add(b); // does not compile
        myBlist.add(c); // does not compile
        A a = myBlist.get(0); 
    }

    public void testContraVariance(List<? super B> myBlist) {
        B b = new B();
        C c = new C();
        myBlist.add(b);
        myBlist.add(c);
        A a = myBlist.get(0); // does not compile
    }
}

回答by anoopelias

The principles behind this in computer science is called

这背后的原理在计算机科学中被称为

  • Covariance: ? extends MyClass,
  • Contravariance: ? super MyClassand
  • Invariance/non-variance: MyClass
  • 协方差:? extends MyClass,
  • 逆变:? super MyClass
  • 不变性/非变化性: MyClass

The picture below should explain the concept. Picture courtesy: Andrey Tyukin

下面的图片应该解释这个概念。图片提供:安德烈秋金

Covariance vs Contravariance

协方差与逆变

回答by Pradeep Kr Kaushal

In a nutshell, three easy rules to remember PECS:

简而言之,记住 PECS 的三个简单规则:

  1. Use the <? extends T>wildcard if you need to retrieve object of type Tfrom a collection.
  2. Use the <? super T>wildcard if you need to put objects of type Tin a collection.
  3. If you need to satisfy both things, well, don't use any wildcard. As simple as that.
  1. <? extends T>如果需要T从集合中检索类型的对象,请使用通配符。
  2. <? super T>如果需要将类型的对象放入集合中,请使用通配符T
  3. 如果您需要同时满足这两件事,那么不要使用任何通配符。就如此容易。

回答by Premraj

PECS (Producer extendsand Consumer super)

PECS(生产者extends和消费者super

mnemonic → Get and Put principle.

助记符 → Get 和 Put 原则。

This principle states that:

该原则指出:

  • Use an extends wildcard when you only get values out of a structure.
  • Use a super wildcard when you only put values into a structure.
  • And don't use a wildcard when you both get and put.
  • 当您仅从结构中获取值时,请使用扩展通配符。
  • 当您只将值放入结构中时,请使用超级通配符。
  • 并且在获取和放置时不要使用通配符。

Example in Java:

Java 中的示例:

class Super {

    Object testCoVariance(){ return null;} //Covariance of return types in the subtype.
    void testContraVariance(Object parameter){} // Contravariance of method arguments in the subtype.
}

class Sub extends Super {

    @Override
    String testCoVariance(){ return null;} //compiles successfully i.e. return type is don't care(String is subtype of Object) 
    @Override
    void testContraVariance(String parameter){} //doesn't support even though String is subtype of Object

}

Liskov substitution principle:if S is a subtype of T, then objects of type T may be replaced with objects of type S.

Liskov 替换原则:如果 S 是 T 的子类型,则类型 T 的对象可以替换为类型 S 的对象。

Within the type system of a programming language, a typing rule

在编程语言的类型系统中,类型规则

  • covariantif it preserves the ordering of types (≤), which orders types from more specific to more generic;
  • contravariantif it reverses this ordering;
  • invariantor nonvariant if neither of these applies.
  • 协变,如果它保留类型的排序 (≤),它将类型从更具体到更通用;
  • 如果颠倒这个顺序,则为逆变
  • 如果这些都不适用,则不变不变

Covariance and contravariance

协方差和逆变

  • Read-only data types (sources) can be covariant;
  • write-only data types (sinks) can be contravariant.
  • Mutable data types which act as both sources and sinks should be invariant.
  • 只读数据类型(源)可以是协变的
  • 只写数据类型(接收器)可以是逆变的
  • 充当源和接收器的可变数据类型应该是不变的

To illustrate this general phenomenon, consider the array type. For the type Animal we can make the type Animal[]

为了说明这种普遍现象,请考虑数组类型。对于类型 Animal 我们可以创建类型 Animal[]

  • covariant: a Cat[] is an Animal[];
  • contravariant: an Animal[] is a Cat[];
  • invariant: an Animal[] is not a Cat[] and a Cat[] is not an Animal[].
  • 协变:猫[] 是动物[];
  • 逆变:Animal[] 是 Cat[];
  • 不变式:Animal[] 不是 Cat[],Cat[] 不是 Animal[]。

Java Examples:

Java 示例:

Object name= new String("prem"); //works
List<Number> numbers = new ArrayList<Integer>();//gets compile time error

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
myNumber[0] = 3.14; //attempt of heap pollution i.e. at runtime gets java.lang.ArrayStoreException: java.lang.Double(we can fool compiler but not run-time)

List<String> list=new ArrayList<>();
list.add("prem");
List<Object> listObject=list; //Type mismatch: cannot convert from List<String> to List<Object> at Compiletime  

more examples

更多例子

bounded(i.e. heading toward somewhere) wildcard: There are 3 different flavours of wildcards:

有界(即朝向某处)通配符:有 3 种不同风格的通配符:

  • In-variance/Non-variance: ?or ? extends Object- UnboundedWildcard. It stands for the family of all types. Use when you both get and put.
  • Co-variance: ? extends T(the family of all types that are subtypes of T) - a wildcard with an upper bound. Tis the upper-most class in the inheritance hierarchy. Use an extendswildcard when you only Getvalues out of a structure.
  • Contra-variance: ? super T( the family of all types that are supertypes of T) - a wildcard with a lower bound. Tis the lower-most class in the inheritance hierarchy. Use a superwildcard when you only Putvalues into a structure.
  • In-variance/Non-variance:?? extends Object-无界通配符。它代表所有类型的家庭。当您同时获取和放置时使用。
  • 协方差:(? extends T作为 的子类型的所有类型的家族 T) - 具有上限的通配符。T是继承层次结构中最上层的类。extends当您只从结构中获取值时使用通配符。
  • 逆变:(? super T作为 的超类型的所有类型的家族 T) - 具有下限的通配符。T是继承层次结构中最底层的类。super当您只值放入结构中时,请使用通配符。

Note: wildcard ?means zero or one time, represents an unknown type. The wildcard can be used as the type of a parameter, never used as a type argument for a generic method invocation, a generic class instance creation.(i.e. when used wildcard that reference not used in elsewhere in program like we use T)

注意:通配符?表示零次或一次,代表未知类型。通配符可以用作参数的类型,永远不会用作泛型方法调用、泛型类实例创建的类型参数。(即,当使用通配符时,在程序中其他地方没有使用的引用,就像我们使用的那样T

enter image description here

在此处输入图片说明

class Shape { void draw() {}}

class Circle extends Shape {void draw() {}}

class Square extends Shape {void draw() {}}

class Rectangle extends Shape {void draw() {}}

public class Test {
 /*
   * Example for an upper bound wildcard (Get values i.e Producer `extends`)
   * 
   * */  

    public void testCoVariance(List<? extends Shape> list) {
        list.add(new Shape()); // Error:  is not applicable for the arguments (Shape) i.e. inheritance is not supporting
        list.add(new Circle()); // Error:  is not applicable for the arguments (Circle) i.e. inheritance is not supporting
        list.add(new Square()); // Error:  is not applicable for the arguments (Square) i.e. inheritance is not supporting
        list.add(new Rectangle()); // Error:  is not applicable for the arguments (Rectangle) i.e. inheritance is not supporting
        Shape shape= list.get(0);//compiles so list act as produces only

        /*You can't add a Shape,Circle,Square,Rectangle to a List<? extends Shape> 
         * You can get an object and know that it will be an Shape
         */         
    }
      /* 
* Example for  a lower bound wildcard (Put values i.e Consumer`super`)
* */
    public void testContraVariance(List<? super Shape> list) {
        list.add(new Shape());//compiles i.e. inheritance is supporting
        list.add(new Circle());//compiles i.e. inheritance is  supporting
        list.add(new Square());//compiles i.e. inheritance is supporting
        list.add(new Rectangle());//compiles i.e. inheritance is supporting
        Shape shape= list.get(0); // Error: Type mismatch, so list acts only as consumer
        Object object= list.get(0); // gets an object, but we don't know what kind of Object it is.

        /*You can add a Shape,Circle,Square,Rectangle to a List<? super Shape> 
        * You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
        */  
    }
}

genericsand examples

泛型示例

回答by Andrejs

(adding an answer because never enough examples with Generics wildcards)

(添加答案,因为没有足够的泛型通配符示例)

       // Source 
       List<Integer> intList = Arrays.asList(1,2,3);
       List<Double> doubleList = Arrays.asList(2.78,3.14);
       List<Number> numList = Arrays.asList(1,2,2.78,3.14,5);

       // Destination
       List<Integer> intList2 = new ArrayList<>();
       List<Double> doublesList2 = new ArrayList<>();
       List<Number> numList2 = new ArrayList<>();

        // Works
        copyElements1(intList,intList2);         // from int to int
        copyElements1(doubleList,doublesList2);  // from double to double


     static <T> void copyElements1(Collection<T> src, Collection<T> dest) {
        for(T n : src){
            dest.add(n);
         }
      }


     // Let's try to copy intList to its supertype
     copyElements1(intList,numList2); // error, method signature just says "T"
                                      // and here the compiler is given 
                                      // two types: Integer and Number, 
                                      // so which one shall it be?

     // PECS to the rescue!
     copyElements2(intList,numList2);  // possible



    // copy Integer (? extends T) to its supertype (Number is super of Integer)
    private static <T> void copyElements2(Collection<? extends T> src, 
                                          Collection<? super T> dest) {
        for(T n : src){
            dest.add(n);
        }
    }

回答by Jason

Remember this:

记住这一点:

Consumer eat supper(super); Producer extendshis parent's factory

消费者吃夜宵(超级);生产者扩建他父母的工厂

回答by Daniel

let's assume this hierarchy:

让我们假设这个层次结构:

class Creature{}// X
class Animal extends Creature{}// Y
class Fish extends Animal{}// Z
class Shark extends Fish{}// A
class HammerSkark extends Shark{}// B
class DeadHammerShark extends HammerSkark{}// C

Let's clarify PE - Producer Extends:

让我们澄清 PE - Producer Extends:

List<? extends Shark> sharks = new ArrayList<>();

Why you cannot add objects that extend "Shark" in this list? like:

为什么不能在此列表中添加扩展“Shark”的对象?喜欢:

sharks.add(new HammerShark());//will result in compilation error

Since you have a list that can be of type A, B or C at runtime, you cannot add any object of type A, B or C in it because you can end up with a combination that is not allowed in java.
In practice, the compiler can indeed see at compiletime that you add a B:

由于您有一个在运行时可以是 A、B 或 C 类型的列表,因此您不能在其中添加任何 A、B 或 C 类型的对象,因为您最终可能会得到 java 中不允许的组合。
实际上,编译器确实可以在编译时看到您添加了 B:

sharks.add(new HammerShark());

...but it has no way to tell if at runtime, your B will be a subtype or supertype of the list type. At runtime the list type can be any of the types A, B, C. So you cannot end up adding HammerSkark (super type) in a list of DeadHammerShark for example.

...但它无法判断在运行时,您的 B 将是列表类型的子类型还是超类型。在运行时,列表类型可以是类型 A、B、C 中的任何一种。因此,例如,您最终不能在 DeadHammerShark 列表中添加 HammerSkark(超级类型)。

*You will say: "OK, but why can't I add HammerSkark in it since it is the smallest type?". Answer: It is the smallest youknow. But HammerSkark can be extended too by somebody else and you end up in the same scenario.

*你会说:“好吧,但是为什么我不能在其中添加 HammerSkark,因为它是最小的类型?”。答案:它是你所知道的最小的。但是 HammerSkark 也可以被其他人扩展,你最终会遇到同样的情况。

Let's clarify CS - Consumer Super:

让我们澄清 CS - Consumer Super:

In the same hierarchy we can try this:

在同一个层次结构中,我们可以试试这个:

List<? super Shark> sharks = new ArrayList<>();

What and why you canadd to this list?

可以添加什么以及为什么要添加到此列表中?

sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());

You can add the above types of objects because anything below shark(A,B,C) will always be subtypes of anything above shark (X,Y,Z). Easy to understand.

您可以添加上述类型的对象,因为鲨鱼(A,B,C)之下的任何东西都将始终是鲨鱼(X,Y,Z)之上的任何事物的子类型。容易理解。

You cannotadd types above Shark, because at runtimethe type of added object can be higher in hierarchy than the declared type of the list(X,Y,Z). This is not allowed.

不能在 Shark 之上添加类型,因为在运行时添加的对象类型在层次结构中可能高于列表(X,Y,Z)的声明类型。这是不允许的。

But why you cannot read from this list? (I mean you can get an element out of it, but you cannot assign it to anything other than Object o):

但是为什么你不能从这个列表中读取?(我的意思是你可以从中获取一个元素,但你不能将它分配给除 Object o 以外的任何东西):

Object o;
o = sharks.get(2);// only assignment that works

Animal s;
s = sharks.get(2);//doen't work

At runtime, the type of list can be any type above A: X, Y, Z, ... The compiler can compile your assignment statement (which seems correct) but, at runtimethe type of s (Animal) can be lower in hierarchy than the declared type of the list(which could be Creature, or higher). This is not allowed.

在运行时,列表的类型可以是任何类型上面答:X,Y,Z,...编译器可以编译你的赋值语句(这似乎是正确的),但是,在运行时的S(动物)的类型可以在较低层次结构比列表的声明类型(可以是 Creature 或更高)。这是不允许的。

To sum up

总结

We use <? super T>to add objects of types equal or below Tto the List.We cannot read from it.
We use <? extends T>to read objects of types equal or below Tfrom list.We cannot add element to it.

我们用<? super T>增加的类型的对象等于或小于TList我们无法从中读出。
我们用来从列表中<? extends T>读取等于或低于类型的对象T我们不能向它添加元素。

回答by contrapost

Using real life example (with some simplifications):

使用现实生活中的例子(有一些简化):

  1. Imagine a freight train with freight cars as analogy to a list.
  2. You can puta cargo in a freight car if the cargo has the same or smaller sizethan the freight car = <? super FreightCarSize>
  3. You can unloada cargo from a freight car if you have enough place(more than the size of the cargo) in your depot = <? extends DepotSize>
  1. 想象一列带有货车的货运列车,就像一个列表。
  2. 如果货物的尺寸与货车相同或更小,您可以货物放入货车 =<? super FreightCarSize>
  3. 如果您的仓库有足够的空间(超过货物的大小),您可以从货车上卸下货物=<? extends DepotSize>