当前位置: 动力学知识库 > 问答 > 编程问答 >

java - What is PECS (Producer Extends Consumer Super)?

问题描述:

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

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

网友答案:

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


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>.

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>.

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

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

The reasoning here is that unlike Collection<? extends Thing>, Collection<? super Thing> can always hold a Thing no 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 Thing to be added; this is what ? super Thing guarantees.

网友答案:

The principles behind this in Computer Science is named after

  • Covariance - ? extends MyClass,
  • Contravariance - ? super MyClass and
  • Invariance/non-Variance - MyClass

The picture below should explain the concept.

Picture courtesy : Andrey Tyukin

网友答案:

PECS (short for "Producer extends and Consumer super") can be explained by : Get and Put Principle

Get And Put Principle (From Java Generics and Collections)

It states,

  1. use an extends wildcard when you only get values out of a structure
  2. use a super wildcard when you only put values into a structure
  3. and don’t use a wildcard when you both get and put.

Let's understand it by example:

1. For Extends Wildcard(get values i.e Producer extends)

Here is a method, that takes a collection of numbers, converts each to a double, and sums them up

public static double sum(Collection<? extends Number> nums) {
   double s = 0.0;
   for (Number num : nums) 
      s += num.doubleValue();
   return s;
}

Let's call the method :

List<Integer>ints = Arrays.asList(1,2,3);
assert sum(ints) == 6.0;
List<Double>doubles = Arrays.asList(2.78,3.14);
assert sum(doubles) == 5.92;
List<Number>nums = Arrays.<Number>asList(1,2,2.78,3.14);
assert sum(nums) == 8.92;

Since, sum() method uses extends, all of the following calls are legal. The first two calls would not be legal if extends was not used.

EXCEPTION : You cannot put anything into a type declared with an extends wildcard—except for the value null, which belongs to every reference type:

List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(null);  // ok
assert nums.toString().equals("[1, 2, null]");

2. For Super Wildcard(put values i.e Consumer super)

Here is a method, that takes a collection of numbers and an int n, and puts the first n integers, starting from zero, into the collection:

public static void count(Collection<? super Integer> ints, int n) {
    for (int i = 0; i < n; i++) ints.add(i);
}

Let's call the method :

List<Integer>ints = new ArrayList<Integer>();
count(ints, 5);
assert ints.toString().equals("[0, 1, 2, 3, 4]");
List<Number>nums = new ArrayList<Number>();
count(nums, 5); nums.add(5.0);
assert nums.toString().equals("[0, 1, 2, 3, 4, 5.0]");
List<Object>objs = new ArrayList<Object>();
count(objs, 5); objs.add("five");
assert objs.toString().equals("[0, 1, 2, 3, 4, five]");

Since, count() method uses super, all of the following calls are legal: The last two calls would not be legal if super was not used.

EXCEPTION : you cannot get anything out from a type declared with a super wildcard—except for a value of type Object, which is a supertype of every reference type:

List<Object> objs = Arrays.<Object>asList(1,"two");
List<? super Integer> ints = objs;
String str = "";
for (Object obj : ints) str += obj.toString();
assert str.equals("1two");

3. When both Get and Put, don't Use wildcard

Whenever you both put values into and get values out of the same structure, you should not use a wildcard.

public static double sumCount(Collection<Number> nums, int n) {
   count(nums, n);
   return sum(nums);
}
网友答案:
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
    }
}
网友答案:

See my answer to another question here. I think it answers your question pretty well. Note that generally you should only be using ? extends T and ? super T for the parameters of some method. Methods should just use T as the type parameter on a generic return type.

网友答案:

PECS(Producer extends and Consumer super) mnemonic --> Input and Output i.e. return type principle

In plain core java without generics:

class Super{
      void testCoVariance(Object parameter){} // method Consumes the Object
      Object testContraVariance(){ return null;} //method Produces the Object
}
class Sub extends Super{

  @Override
  void testCoVariance(String parameter){} //Note:java doesn't support eventhough String is subtype of Object


  @Override
  String testContraVariance(){ return null;}//compiles successfully because return type is don't care 

}

This is possible for Generics using wildcards:

Note: wildcard ? means zero or one time.

A wildcard describes a family of types. There are 3 different flavours of wildcards:

  • ? or ? extends Object - the unbounded wildcard. It stands for the family of all types. (in-variance/non-variance)
  • ? extends T (Get values i.e Producer extends) - a wildcard with an upper bound. It stands for the family of all types that are subtypes of T, including type T. Has to be an ancestor of a specific type.you cannot add elements to collection, only read them.(co-variance)
  • ? super T (Put values i.e Consumersuper) - a wildcard with a lower bound. It stands for the family of all types that are supertypes of T, including type T. Has to extend a specific type.you cannot read elements to collection, only add them.(contra-variance)

import java.util.List;

class Shape {
    void draw() {
    }
}

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

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

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



public class TestContraVariance {
 /*
   * Example for an upper bound wildcard (Get values i.e Producer `extends`)
   * We can not safely add, because we may have  `List<Circle>`, `List<Square>`and List<Rectangle>.  
   * 
   * */  

     public void testCoVariance(List<? extends Shape> list) {
         list.add(new Shape()); // does not compile
         list.add(new Circle()); // does not compile
         list.add(new Square()); // does not compile
         list.add(new Rectangle()); // does not compile
         Shape shape= list.get(0);//compiles
        }
      /* 
 * Example for  a lower bound wildcard (Put values i.e Consumer`super`)
 * We can not safely get, because we may have  `List<Circle>`, `List<Square>`and List<Rectangle>.
 * */
     public void testContraVariance(List<? super Shape> list) {
            list.add(new Shape());//compiles
            list.add(new Circle());//compiles
            list.add(new Square());//compiles
            list.add(new Rectangle());//compiles
            Shape shape= list.get(0); // does not compile
        }


}

Angelika Langer is best to learn generics

Guidelines for Wildcard Use

One of the more confusing aspects when learning to program with generics is determining when to use an upper bounded wildcard and when to use a lower bounded wildcard.

For purposes of this discussion, it is helpful to think of variables as providing one of two functions:

An "In" Variable
An "in" variable serves up data to the code. Imagine a copy method with two arguments: copy(src, dest). The src argument provides the data to be copied, so it is the "in" parameter.

An "Out" Variable
An "out" variable holds data for use elsewhere. In the copy example, copy(src, dest), the dest argument accepts data, so it is the "out" parameter.


Wildcard Guidelines:

  • An "in" variable is defined with an upper bounded wildcard, using the extends keyword.
  • An "out" variable is defined with a lower bounded wildcard, using the super keyword.
  • In the case where the "in" variable can be accessed using methods defined in the Object class, use an unbounded wildcard.
  • In the case where the code needs to access the variable as both an "in" and an "out" variable, do not use a wildcard.

source

网友答案:

In nutshell easy to remember PECS

  1. Use the <? extends T> wildcard if you need to retrieve object of type T from a collection.
  2. Use the <? super T> wildcard if you need to put objects of type T in a collection.
  3. If you need to satisfy both things, well, don’t use any wildcard. As simple as it is.
分享给朋友:
您可能感兴趣的文章:
随机阅读: