What is PECS (Producer Extends Consumer Super)?
Asked Answered
E

17

906

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?

Encrinite answered 27/4, 2010 at 17:16 Comment(1)
A very good explanation with an example @ youtube.com/watch?v=34oiEq9nD0M&feature=youtu.be&t=1630 which explains super part but, gives an idea of another.Coloring
D
1043

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 (except null) 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.

Dichotomize answered 27/4, 2010 at 17:37 Comment(16)
I'm always trying to think about it this way: A producer is allowed to produce something more specific, hence extends, a consumer is allowed to accept something more general, hence super.Tiphanie
Another way to remember the producer/consumer distinction is to think of a method signature. If you have a method doSomethingWithList(List list), you are consuming the list and so will need covariance / extends (or an invariant List). On the other hand if your method is List doSomethingProvidingList, then you are producing the List and will need contravariance / super (or an invariant List).Portable
@MichaelMyers: Why can't we simply use a parameterized type for both these cases? Is there any specific advantage of using wildcards here, or is it just a means of improving readability similar to, say, using references to const as method parameters in C++ to signify that the method does not modify the arguments?Rhoden
@Raman, I think you just confused it. In doSthWithList( you can have List<? super Thing> ), since you are a consumer, you can use super (remember, CS). However, it's List<? extends Thing> getList() since you are allowed to return something more specific when producing (PE).Gertrudegertrudis
what if we want to go through and add to the collection?Farreaching
@hajder Then your only option is to use the exact type: Collection<Thing>. The reason for this is that you must be sure that the get method will return something that you can cast to Thing, and the add method accepts a Thing, which it can cast to the actual element type. The only type that satisfies both criteria is Thing itself.Confiscable
@hajder Though note that here "going through" assumes that you really need to cast the elements to Thing.Confiscable
@Chatterjee: the reason for using wildcards is flexibility. By declaring that a parameter is of type Collection<? super Thing> you give the caller more flexibility as she can invoke your method not only with a Collection<Thing> as an argument but also with a Collection<SomeSupertypeOfThing> as an argument.Jaquelynjaquenetta
@MichaelMyers: For the case2, Collection <? super Thing> also is iterable, right?Viyella
@EricZhang: When you are adding to a collection, it does not matter whether or not the collection is iterable.Dichotomize
PECS is counter intuitive, If I am taking something from the list so I am consuming items or producing?Shambles
@Shambles I share your sentiment. If a method do get() from the list, the method would be considered a Consumer<T>, and the list is considered a provider; but the rule of PECS is “from the list’s point of view”, thus ‘extends’ is called for. It should be GEPS: get extends; put super.Vilipend
In case I, all subtype of Thing is Thing, why adding Thing instance is not allowed? As all operation only happen in Thing type. Can you give a concrete example why adding Thing instance to the list in case I will cause issue?Wisent
@HKIT: It isn't that adding a Thing to the Collection<? extends Thing> will cause an issue. The language simply won't permit it and your program won't compile. The contrast is rather between Collection<? extends Thing> and simply Collection<Thing>. And the difference is that a Collection<MyThingSubType> is not a Collection<Thing> and couldn't be passed to a method expecting such, but it is a Collection<? extends Thing> and could be passed to such a method.Dichotomize
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. does this mean that the Thing will be downcast to the class ? when added to the collection?Wines
@PJ_Finnegan: Casting only matters when you're immediately acting on an object. It doesn't lose its runtime type when it's added to the collection, if that's what you're asking.Dichotomize
S
679

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

Sadowski answered 2/11, 2013 at 6:34 Comment(8)
Hey everyone. I'm Andrey Tyukin, I just wanted to confirm that anoopelias & DaoWen contacted me and obtained my permission to use the sketch, it's licensed under (CC)-BY-SA. Thx @ Anoop for giving it a second life^^ @Brian Agnew: (on "few votes"): That's because it's a sketch for Scala, it uses Scala syntax and assumes declaration-site variance, which is quite different to Java's weird call-site variance... Maybe I should write a more detailed answer that clearly shows how this sketch applies to Java...Wheelwright
This is one of the simplest and clearest explanations for Covariance and Contravariance that I have ever found!Zusman
@Andrey Tyukin Hi, I also want to use this image. How can I contact you?Rectocele
If you have any questions about this illustration, we can discuss them in the chatroom: chat.stackoverflow.com/rooms/145734/…Wheelwright
I finally added an explanation for Scala's notions of covariance and contravariance, with a detailed explanation and compilable code.Wheelwright
Can someone explain the use/application of <? super MyClass>. Because you can put MyClass and its subclass objects into it, but if you want to take out stuff from that collection. They can be taken out only as Objects.Traffic
@ChetanGowda Here is a rather detailed example that explains why <? super X> is useful, and what happens if you omit it: Java 8 Comparator comparing() static function.Wheelwright
Are the angle brackets directional or comparative?Rosefish
C
94

PECS is a mnemonic device in Java generics that helps us remember which keyword to use when specifying bounds on generic type parameters.

1 Producer Extends:

  • Applies to return types and variables with upper bounds.
  • Represents a producer of values.
  • Allows you to return a more specific subtype of the declared type (covariant).
  • Covariance is a hierarchical relationship i.e. "is-a" relationship: Cat as "is-a" Animal.

2 Consumer Super:

  • Applies to input parameters and variables with lower bounds.
  • Represents a consumer of values.
  • Allows you to accept a more general supertype of the declared type (contravariant).
  • Contravariance Reversal of Subtype Relationship: Animal as "can hold" Cat.

3. Invariant/Nonvariant types

  • (without bounds) have fixed relationships and don't benefit from PECS.
  • Fixed relationship i.e. the type must remain exactly as specified, without any flexibility to accept subtypes or supertypes: i.e. where both input and output types are fixed.

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

enter image description here Image src

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

  • Co-variance: ? extends T ( Reign of T descendants) - 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. Upper bounds promote safe substitution of subtypes.
    For example, if T extends Animal, T can be Animal itself or any of its subtypes like Cat, Dog, etc., but it cannot be a type "higher" than Animal, like Object.
  • Contra-variance: ? super T ( Reign of T ancestor) - 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. Lower bounds promote safe substitution of supertypes.
  • In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put.

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.
         */
    }
}

More coding examples
Visualising the concept

Caledonian answered 26/7, 2015 at 6:33 Comment(5)
Hey, I just wanted to know what you meant with the last sentense: "If you think my analogy is wrong please update". Do you mean if it is ethically wrong (which is subjective) or if it is wrong in the context of programming (which is objective: no, it's not wrong)? I would like to replace it with a more neutral example which is universally acceptable independent of cultural norms and ethical believes; If that is OK with you.Meader
at last I could get it. Nice explanation.Bollay
@Premraj, In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put., I cannot add element to List<?> or List<? extends Object>, so I don't understand why it can be Use when you both get and put.Maun
@Maun - That part of the answer is misleading. ? - the "unbounded wildcard" - corresponds with the exact opposite of invariance. Please refer to the following documentation: docs.oracle.com/javase/tutorial/java/generics/… which states: In the case where the code needs to access the variable as both an "in" and an "out" variable, do not use a wildcard. (They are using "in" and "out" as synonymous with "get" and "put"). With the exception of null you can't add to a Collection parameterized with ?.Achondroplasia
https://mcmap.net/q/15463/-what-is-a-difference-between-lt-super-e-gt-and-lt-extends-e-gt for moreCaledonian
J
34
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
    }
}
Jannet answered 7/10, 2013 at 18:15 Comment(4)
So "? extends B" should be interpreted as "? B extends". It's something that B extends so that would include all the super classes of B up to Object, excluding B itself. Thanks for the code!Plural
@SaurabhPatil No, ? extends B means B and anything extending B.Shoot
What is the significance of the lines marked "does not compile"? My understanding is that we cannot add anything other than null to an "extends" list.; we cannot add As or Bs or Cs. CoVarience might be demonstrated by the failure to compile C c = myBlist.get(0); Similarly in the Contrvarience methos all gets fail except assignment to Object. Failure to a add an A demonstrates Contravarience.Unpaid
Downvoted because there is no explanation and as the example stands I think it does not demonstrate what it tries to demonstrate. Happy to upvote if an explanation is added.Unpaid
J
32

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.
Jaella answered 13/11, 2014 at 6:51 Comment(0)
W
29

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.

Wardmote answered 27/4, 2010 at 17:32 Comment(2)
Does this principle only hold for Collections? It makes sense when one tries to correlate it with a list. If you think about the signature of sort(List<T>,Comparator<? super T>) ---> here the Comparator uses super so it means it is a consumer in PECS context. When you look at the implementation for instance like : public int compare(Person a, Person b) { return a.age < b.age ? -1 : a.age == b.age ? 0 : 1; } I feel like Person does not consume anything but produces age. That makes me confused. Is there a flaw in my reasoning or PECS only holds for Collections?Adventure
@FatihArslan don’t look into the comparator implementation. It’s irrelevant. The method sort(List<T>,Comparator<? super T>) declares the type bounds and in that sort method, the comparator consumes T instances.Hebraist
L
19

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.

Lancastrian answered 23/9, 2018 at 17:42 Comment(1)
Thank you so very much for your answer. Your very concrete examples with the generic Lists and why we can and can't do certain operations finally made it click for me.Sanitarium
P
19

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! :)

Presley answered 18/11, 2020 at 6:21 Comment(1)
The code example you gave is the gist!Clinical
H
16

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

Hades answered 22/1, 2020 at 20:57 Comment(2)
I've run into that same mental collision and would tend to agree except that PECS doesn't specify the naming of code and the type boundaries themselves are set on the Collection declarations. Moreover as far as naming is concerned you often have names for producing/consuming Collections like src and dst. So you're dealing with both code and containers at the same time and I've ended up thinking about it along those lines - "consuming code" consumes from a producing container, and "producing code" produces for a consuming container.Achondroplasia
I think PECS is a terrible nomenclature. I couldn't wrap my head around it until you explained that it was from the perspective of the data collection itself. Someone mentioned using GEPS instead (Get Extends, Put Super), and I think this is a much more useful mnemonic.. I was able to accurately reconstruct the Collections.copy(List dest, List src) signature based on the GEPS mnemonic..Muskeg
T
10

(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);
        }
    }
Tratner answered 17/5, 2017 at 18:12 Comment(0)
A
10

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

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<? super T> consumer, List<? extends T> producer is simply ensuring that the compiler can enforce the standard "IS-A" inheritance and assignment rules.

Consider the following toy code:

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

The consumer type is the reference - the "left hand side" of the assignment - and it must be T or a super-type of T - <? super T> ensures that.

For producer the concern is the same it's just inverted: the producer type is the "referent" - the "right hand side" of the assignment - and it must be T or a sub-type of T - <? extends T> ensures that.

Achondroplasia answered 18/4, 2020 at 18:30 Comment(2)
For those like me, who didn't understand the "IS-A" terminology: en.wikipedia.org/wiki/Is-aKrystinakrystle
@MichalVician Imagine a class A and a class B, with each having a single public method defined - a() and b() - respectively. If B extends A, then the result is that B contains both a() and b(). B then "IS-A" A because it fully represents A's "interface." But the same can't be said of A - A is not a B, we only know that B is (at least) an A, since it extends AAchondroplasia
B
4

Covariance: accept subtypes
Contravariance: accept supertypes

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

Bartonbartosch answered 7/2, 2019 at 15:33 Comment(0)
H
4

PECS (Producer extends and Consumer super)

[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);
Heatherheatherly answered 3/6, 2020 at 10:55 Comment(1)
to me it is the best answer talking about why we cannot add item to a wildcard upper boundWisent
S
3

Remember this:

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

Sancho answered 19/2, 2018 at 9:50 Comment(0)
Y
1

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>
Youngs answered 20/11, 2018 at 13:47 Comment(0)
C
0

PECS: Producer extends and Consumer super

Prerequisites for understanding:

  • Generics and generic wildcards
  • Polymorphism, Subtyping and Supertyping

Lets say we have a type which takes a generic type parameter T, for example List<T>. When we write code it can be potentially beneficial to also allow subtypes or supertypes of our generic type parameter T. This relaxes the restraints for the user of the API and can make the code more flexible.

Let first see what we gain relaxing these restrictions. Lets say we have the following 3 classes:

class BaseAnimal{};

class Animal extends BaseAnimal{};

class Duck extends Animal{};

and we are building a public method which takes a list<Animal>

  1. If we use a super List<? super Animal> instead of List<Animal> we now can pass in more lists to satisfy the requirement of our method. We now can pass in either List<Animal> or List<BaseAnimal> even List<Object>
  2. If we use an extends List<? extends Animal> instead of List<Animal> we now can pass in more lists to satisfy the requirement of our method. We now can pass in either List<Animal> or List<Duck>

However this poses the following 2 restrictions:

  1. If we use a super type like List<? super Animal> we don't know the exact type of List<T> it will be. It could be either a list of List<Animal> or List<BaseAnimal> or List<Object>. We have no way of knowing. This means we can never get a value out of this List because we do not know what the type will be. However we can put any data type which is Animal or extends it into the List. Because we can only put data into the List it is called a consumer of data.
  2. If we use an extends List<? extends Animal> instead of List<Animal>. We also don't know what the exact type is. It can either be List<Animal> or List<Duck>. We can't add something to the List now because we can never certainly know what the type is. However we can pull something out because we always know that anything which comes out of the list is a subtype of Animal. Because we can only pull data out of the List is it called a producer of data.

Here is a simple program to illustrate the relaxation of the type restrictions:

import java.util.ArrayList;
import java.util.List;

public class Generics {
    public static void main(String[] args) {

        Generics generics = new Generics();

        generics.producerExtends(new ArrayList<Duck>());
        generics.producerExtends(new ArrayList<Animal>());

        generics.consumerSuper(new ArrayList<Object>());
        generics.consumerSuper(new ArrayList<Animal>());

    }

    //  ? extends T   is an upper bound
    public void producerExtends (List<? extends Animal> list) {

        // Following are illegal since we never know exactly what type the list will be
        // list.add(new Duck());
        // list.add(new Animal());
        
        // We can read from it since we are always getting an Animal or subclass from it
        // However we can read them as an animal type, so this compiles fine
        if (list.size() > 0) {
            Animal animal = list.get(0);
        }
    }

    // ? extends T   is a lower bound
    public void consumerSuper (List<? super Animal> list) {
        // It will be either a list of Animal or a superclass of it
        // Therefore we can add any type which extends animals
        list.add(new Duck());
        list.add(new Animal());

        // Compiler won't allow this it could potentially be a super type of Animal
        // Animal animal = list.get(0);
    }
Countrybred answered 1/4, 2022 at 14:18 Comment(0)
S
0

Here is an example for both cases:

public class PECSExample {

    public static void main(String[] args) {
        List<C> producerCList = new ArrayList<>();
        List<D> producerDList = new ArrayList<>();
        List<E> producerEList = new ArrayList<>();
        producerCList.add(new E());
        producerDList.add(new E());
        producerEList.add(new E());
        producerExtends(producerCList);
        producerExtends(producerDList);
        producerExtends(producerEList);

        List<Object> consumerObjectList = new ArrayList<>();
        List<A> consumerAList = new ArrayList<>();
        List<B> consumerBList = new ArrayList<>();
        consumerSuper(consumerObjectList);
        consumerSuper(consumerAList);
        consumerSuper(consumerBList);
    }
    public static void producerExtends(List<? extends C> producerList) {
        System.out.println("Producer printing");
        producerList.forEach(System.out::println);
    }
    public static void consumerSuper(List<? super C> consumerList) {
        consumerList.add(new C());
        consumerList.add(new D());
        consumerList.add(new E());
        System.out.println("Consumer printing");
        consumerList.forEach(System.out::println);
    }
}
class A {}
class B extends A {}
class C extends B {}
class D extends C {}
class E extends D {}

Output:

Producer printing
collections.E@6d311334
Producer printing
collections.E@682a0b20
Producer printing
collections.E@3d075dc0
Consumer printing
collections.C@448139f0
collections.D@7cca494b
collections.E@7ba4f24f
Consumer printing
collections.C@3b9a45b3
collections.D@7699a589
collections.E@58372a00
Consumer printing
collections.C@4dd8dc3
collections.D@6d03e736
collections.E@568db2f2
Sumikosumma answered 30/3 at 11:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.