Home » Java » What is PECS (Producer Extends Consumer Super)?

What is PECS (Producer Extends Consumer Super)?

Posted by: admin November 2, 2017 Leave a comment

Questions:

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?

Answers:

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.

Questions:
Answers:

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

Covariance vs Contravariance

Questions:
Answers:

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);
}

Questions:
Answers:
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
    }
}

Questions:
Answers:

PECS(Producer extends and Consumer super)

mnemonic –> Input and Output (i.e. return type principle)

In Java, arguments and type arguments does not support inheritance as follows.

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){} //doesn't support eventhough String is subtype of Object

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

}

for generics type arguments:

class Shape {
}


class DrawShape{
    void draw(ArrayList<Shape> al){

    }
}

class DrawObject{
    void draw(ArrayList<Object> al){

    }
}

public class Test {
    public static void main(String[] args) {
        ArrayList rawArrayList=new ArrayList();
        ArrayList<Object> arrayListObject=new ArrayList<>();
        ArrayList<Shape> arrayListShape=new ArrayList<>();

        DrawShape drawShape=new DrawShape();
        drawShape.draw(rawArrayList); // compiles
        drawShape.draw(arrayListObject); // Error: the type DrawShape is not applicable for the arguments  
        drawShape.draw(arrayListShape); // compiles

        DrawObject drawObject = new DrawObject();
        drawObject.draw(rawArrayList); // compiles
        drawObject.draw(arrayListObject); // compiles 
        drawObject.draw(arrayListShape); // Error: the type DrawObject is not applicable for the arguments 
    }
}

These kind of problem are solved by using Generics with wildcards:

Note: wildcard ? means zero or one time.

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

  • In-variance/Non-variance: ? or ? extends Object – the unbounded wildcard. It stands for the family of all types. (in-variance/non-variance)
  • Co-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)
  • Contra-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)

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 TestContraVariance {
 /*
   * 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

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


}

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

Analogy: source

Covariance and Contravariance with Horses and People

In the following picture, the type A is a set of horses, the type B is a set of people, and the arrows describe a function of type Function1[A,B].

The variance tells me that I can use this function with a more flexible type than plain Function1[A,B], I can use it with any type Function1[X,Y] where X is a subset of A and Y is a superset of B. “Using with this type” here means that I call this function in a context where I know (or, rather, the compiler can prove) that the argument passed to the function is always a member of the set X, and the code handling the result of the function can process any value that is a member of set Y. This is illustrated in the following picture:

enter image description here

But the full type of Function1[-A,+B] contains these funny little signs that denote the variance, the minus meaning that it is contravariant in A, the plus meaning that it is covariant in B.

The variance tells me that I can use this function with a more flexible type than plain Function1[A,B], I can use it with any type Function1[X,Y] where X is a subset of A and Y is a superset of B. “Using with this type” here means that I call this function in a context where I know (or, rather, the compiler can prove) that the argument passed to the function is always a member of the set X, and the code handling the result of the function can process any value that is a member of set Y. This is illustrated in the following picture:
enter image description here

It doesn’t hurt at all if I restrict the function to just the brown horses, it will always return a well defined person for that horse. But it would fail if I called it with a cow.

Questions:
Answers:

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

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

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

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.

Questions:
Answers:

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.
Questions:
Answers:

(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);
        }
    }