[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?

This question is related to java generics super bounded-wildcard pecs

The answer is


The PECS "rule" just ensures that the following is legal:

  • Consumer: whatever ? is, it can legally refer to T
  • Producer: whatever ? is, it can legally be referred to by T

The typical pairing along the lines of List<? extends T> producer, List<? super T> consumer is simply ensuring that the compiler can enforce the standard "IS-A" inheritance relationship rules. If we could do so legally, it might be simpler to say <T extends ?>, <? extends T> (or better yet in Scala, as you can see above, it's [-T], [+T]. Unfortunately the best we can do is <? super T>, <? extends T>.

When I first encountered this and broke it down in my head the mechanics made sense but the code itself continued to look confusing to me - I kept thinking "it seems like the bounds shouldn't need to be inverted like that" - even though I was clear on the above - that it's simply about guaranteeing compliance with the standard rules of reference.

What helped me was looking at it using ordinary assignment as an analogy.

Consider the following (not production ready) toy code:

// copies the elements of 'producer' into 'consumer'
static <T> void copy(List<? extends T> producer, List<? super T> consumer) {
   for(T t : producer)
       consumer.add(t);
}

Illustrating this in terms of the assignment analogy, for consumer the ? wildcard (unknown type) is the reference - the "left hand side" of the assignment - and <? super T> ensures that whatever ? is, T "IS-A" ? - that T can be assigned to it, because ? is a super type (or at most the same type) as T.

For producer the concern is the same it's just inverted: producer's ? wildcard (unknown type) is the referent - the "right hand side" of the assignment - and <? extends T> ensures that whatever ? is, ? "IS-A" T - that it can be assigned to a T, because ? is a sub type (or at least the same type) as T.


The principles behind this in computer science is called

  • 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


PECS (Producer extends and Consumer super)

mnemonic ? Get and Put principle.

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:

class Super {
        Number testCoVariance() {
            return null;
        }
        void testContraVariance(Number parameter) {
        } 
    }
    
    class Sub extends Super {
        @Override
        Integer testCoVariance() {
            return null;
        } //compiles successfully i.e. return type is don't care(Integer is subtype of Number)
        @Override
        void testContraVariance(Integer parameter) {
        } //doesn't support even though Integer is subtype of Number
    }

The Liskov Substitution Principle (LSP) states that “objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program”.

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

  • covariant if it preserves the ordering of types (=), which orders types from more specific to more generic;
  • contravariant if it reverses this ordering;
  • invariant or 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[]

  • 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[].

Java Examples:

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

enter image description here Image src

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

  • In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. 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. T is the upper-most class in the inheritance hierarchy. Use an extends wildcard when you only Get values out of a structure.
  • Contra-variance: ? super T ( the family of all types that are supertypes of T) - a wildcard with a lower bound. T is the lower-most class in the inheritance hierarchy. Use a super wildcard when you only Put values into a structure.

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)

enter image description here

 import java.util.ArrayList;
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 Test {

    public static void main(String[] args) {
        //? extends Shape i.e. can use any sub type of Shape, here Shape is Upper Bound in inheritance hierarchy
        List<? extends Shape> intList5 = new ArrayList<Shape>();
        List<? extends Shape> intList6 = new ArrayList<Cricle>();
        List<? extends Shape> intList7 = new ArrayList<Rectangle>();
        List<? extends Shape> intList9 = new ArrayList<Object>();//ERROR.


        //? super Shape i.e. can use any super type of Shape, here Shape is Lower Bound in inheritance hierarchy
        List<? super Shape> inList5 = new ArrayList<Shape>();
        List<? super Shape> inList6 = new ArrayList<Object>();
        List<? super Shape> inList7 = new ArrayList<Circle>(); //ERROR.

        //-----------------------------------------------------------
        Circle circle = new Circle();
        Shape shape = circle; // OK. Circle IS-A Shape

        List<Circle> circles = new ArrayList<>();
        List<Shape> shapes = circles; // ERROR. List<Circle> is not subtype of List<Shape> even when Circle IS-A Shape

        List<? extends Circle> circles2 = new ArrayList<>();
        List<? extends Shape> shapes2 = circles2; // OK. List<? extends Circle> is subtype of List<? extends Shape>


        //-----------------------------------------------------------
        Shape shape2 = new Shape();
        Circle circle2= (Circle) shape2; // OK. with type casting

        List<Shape> shapes3 = new ArrayList<>();
        List<Circle> circles3 = shapes3; //ERROR. List<Circle> is not subtype of  List<Shape> even Circle is subetype of Shape

        List<? super Shape> shapes4 = new ArrayList<>();
        List<? super Circle> circles4 = shapes4; //OK.
    }

    
    
    /*
     * Example for an upper bound wildcard (Get values i.e Producer `extends`)
     *
     * */
    public void testCoVariance(List<? extends Shape> list) {
        list.add(new Object());//ERROR
        list.add(new Shape()); //ERROR
        list.add(new Circle()); // ERROR
        list.add(new Square()); // ERROR
        list.add(new Rectangle()); // ERROR
        Shape shape= list.get(0);//OK 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 Object());//ERROR
        list.add(new Shape());//OK
        list.add(new Circle());//OK
        list.add(new Square());//OK
        list.add(new Rectangle());//OK
        Shape shape= list.get(0); // ERROR. Type mismatch, so list acts only as consumer
        Object object= list.get(0); //OK 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.
         */
    }
}

generics and examples

Covariance and contravariance determine compatibility based on types. In either case, variance is a directed relation. Covariance can be translated as "different in the same direction," or with-different, whereas contravariance means "different in the opposite direction," or against-different. Covariant and contravariant types are not the same, but there is a correlation between them. The names imply the direction of the correlation.


Using real life example (with some simplifications):

  1. Imagine a freight train with freight cars as analogy to a list.
  2. You can put a cargo in a freight car if the cargo has the same or smaller size than the freight car = <? super FreightCarSize>
  3. You can unload a cargo from a freight car if you have enough place (more than the size of the cargo) in your depot = <? extends DepotSize>

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.


In a nutshell, three easy rules 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 that.

[Covariance and contravariance]

Lets take a look at example

public class A { }
//B is A
public class B extends A { }
//C is A
public class C extends A { }

Generics allows you to work with Types dynamically in a safe way

//ListA
List<A> listA = new ArrayList<A>();

//add
listA.add(new A());
listA.add(new B());
listA.add(new C());

//get
A a0 = listA.get(0);
A a1 = listA.get(1);
A a2 = listA.get(2);
//ListB
List<B> listB = new ArrayList<B>();

//add
listB.add(new B());

//get
B b0 = listB.get(0);

Problem

Since Java's Collection is a reference type as a result we have next issues:

Problem #1

//not compiled
//danger of **adding** non-B objects using listA reference
listA = listB;

*Swift's generic does not have such problem because Collection is Value type[About] therefore a new collection is created

Problem #2

//not compiled
//danger of **getting** non-B objects using listB reference
listB = listA;

The solution - Generic Wildcards

Wildcard is a reference type feature and it can not be instantiated directly

Solution #1 <? super A> aka lower bound aka contravariance aka consumers guarantees that it is operates by A and all superclasses, that is why it is safe to add

List<? super A> listSuperA;
listSuperA = listA;
listSuperA = new ArrayList<Object>();

//add
listSuperA.add(new A());
listSuperA.add(new B());

//get
Object o0 = listSuperA.get(0);

Solution #2

<? extends A> aka upper bound aka covariance aka producers guarantees that it is operates by A and all subclasses, that is why it is safe to get and cast

List<? extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;

//get
A a0 = listExtendsA.get(0);

let’s try visualizing this concept.

<? super SomeType> is an “undefined(yet)” type, but that undefined type should be a superclass of the ‘SomeType’ class.

The same goes for <? extends SomeType>. It’s a type that should extend the ‘SomeType’ class (it should be a child class of the ‘SomeType’ class).

If we consider the concept of 'class inheritance' in a Venn diagram, an example would be like this:

enter image description here

Mammal class extends Animal class (Animal class is a superclass of Mammal class).

Cat/Dog class extends Mammal class (Mammal class is a superclass of Cat/Dog class).

Then, let’s think about the ‘circles’ in the above diagram as a ‘box’ that has a physical volume.

enter image description here

You CAN’T put a bigger box into a smaller one.

You can ONLY put a smaller box into a bigger one.

When you say <? super SomeType>, you wanna describe a ‘box’ that is the same size or bigger than the ‘SomeType’ box.

If you say <? extends SomeType>, then you wanna describe a ‘box’ that is the same size or smaller than the ‘SomeType’ box.

so what is PECS anyway?

An example of a ‘Producer’ is a List which we only read from.

An example of a ‘Consumer’ is a List which we only write into.

Just keep in mind this:

  • We ‘read’ from a ‘producer’, and take that stuff into our own box.

  • And we ‘write’ our own box into a ‘consumer’.

So, we need to read(take) something from a ‘producer’ and put that into our ‘box’. This means that any boxes taken from the producer should NOT be bigger than our ‘box’. That’s why “Producer Extends.”

“Extends” means a smaller box(smaller circle in the Venn diagram above). The boxes of a producer should be smaller than our own box, because we are gonna take those boxes from the producer and put them into our own box. We can’t put anything bigger than our box!

Also, we need to write(put) our own ‘box’ into a ‘consumer’. This means that the boxes of the consumer should NOT be smaller than our own box. That’s why “Consumer Super.”

“Super” means a bigger box(bigger circle in the Venn diagram above). If we want to put our own boxes into a consumer, the boxes of the consumer should be bigger than our box!

Now we can easily understand this example:

public class Collections { 
  public static <T> void copy(List<? super T> dest, List<? extends T> src) {
      for (int i = 0; i < src.size(); i++) 
        dest.set(i, src.get(i)); 
  } 
}

In the above example, we want to read(take) something from src and write(put) them into dest. So the src is a “Producer” and its “boxes” should be smaller(more specific) than some type T.

Vice versa, the dest is a “Consumer” and its “boxes” should be bigger(more general) than some type T.

If the “boxes” of the src were bigger than that of the dest, we couldn’t put those big boxes into the smaller boxes the dest has.

If anyone reads this, I hope it helps you better understand “Producer Extends, Consumer Super.”

Happy coding! :)


Covariance: accept subtypes
Contravariance: accept supertypes

Covariant types are read-only, while contravariant types are write-only.


This is the clearest, simplest way for me think of extends vs. super:

  • extends is for reading

  • super is for writing

I find "PECS" to be a non-obvious way to think of things regarding who is the "producer" and who is the "consumer". "PECS" is defined from the perspective of the data collection itself – the collection "consumes" if objects are being written to it (it is consuming objects from calling code), and it "produces" if objects are being read from it (it is producing objects to some calling code). This is counter to how everything else is named though. Standard Java APIs are named from the perspective of the calling code, not the collection itself. For example, a collection-centric view of java.util.List should have a method named "receive()" instead of "add()" – after all, the calling code adds the element, but the list itself receives the element.

I think it's more intuitive, natural and consistent to think of things from the perspective of the code that interacts with the collection – does the code "read from" or "write to" the collection? Following that, any code writing to the collection would be the "producer", and any code reading from the collection would be the "consumer".


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:

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

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

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:

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.

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

Let's clarify CS - Consumer Super:

In the same hierarchy we can try this:

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

What and why you can add 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.

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

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;
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 runtime the 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.

To sum up

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


Remember this:

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


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

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

Examples related to java

Under what circumstances can I call findViewById with an Options Menu / Action Bar item? How much should a function trust another function How to implement a simple scenario the OO way Two constructors How do I get some variable from another class in Java? this in equals method How to split a string in two and store it in a field How to do perspective fixing? String index out of range: 4 My eclipse won't open, i download the bundle pack it keeps saying error log

Examples related to generics

Instantiating a generic type Are these methods thread safe? The given key was not present in the dictionary. Which key? Using Java generics for JPA findAll() query with WHERE clause Using Spring RestTemplate in generic method with generic parameter How to create a generic array? Create a List of primitive int? How to have Java method return generic list of any type? Create a new object from type parameter in generic class What is the "proper" way to cast Hibernate Query.list() to List<Type>?

Examples related to super

Equivalent of Super Keyword in C# super() raises "TypeError: must be type, not classobj" for new-style class Java: Calling a super method which calls an overridden method When do I use super()? super() in Java What is PECS (Producer Extends Consumer Super)? super() fails with error: TypeError "argument 1 must be type, not classobj" when parent does not inherit from object Understanding Python super() with __init__() methods What does 'super' do in Python?

Examples related to bounded-wildcard

What is PECS (Producer Extends Consumer Super)? Java Generics With a Class & an Interface - Together

Examples related to pecs

What is PECS (Producer Extends Consumer Super)?