What Is Pecs (Producer Extends Consumer Super)

What is PECS (Producer Extends Consumer 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 (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.

Java generics PECS

A nice mnemonic you can use is to imagine returns for extends and accepts for super.

So a Tree<? extends T> reads Tree<? returns T>, which means that you can call the methods in Tree that return T, but not the methods that accept T as an argument type.

Why Comparable and Comparator are consumers in PECS wildcard types in Java

A nice analogy can be drawn to the interfaces Consumer<T> and Supplier<T> (with Supplier being analogous to Producer). A Consumer<T> is a function that takes in a T, while a Supplier<T> is a function that returns a T. Notice that we are talking about method signatures and return type, we say nothing about the semantics of the method. This is a core property of PECS: it is independent of the semantics and can be determined solely on the signature and return type of the methods used.

Looking at Comparable<T> and Comparator<T>, we find that both have methods (int compareTo(T) and int compare(T, T)) that take in, i.e. consume, T's.

For the collections, we have to look on how we use the collection, i.e. if we are using producer- or consumer-methods:

  • If we retrieve data from the collection (T get(int), iterator, ...), the list produces values for us and we use ? extends T.
  • If we use the collection to store data, (i.e. we call add(T), addAll(Collection<T>), contains(T), ...), we call consuming methods, thus the method is a consumer of our data and we use ? super T.
  • If we use a collection to both store and retrieve values, the collection acts as a consumer and producer at the same time, thus we have to use the precise T, neither using ... extends ... nor ... super ....

Difference between ? super T and ? extends T in Java

extends

The wildcard declaration of List<? extends Number> foo3 means that any of these are legal assignments:

List<? extends Number> foo3 = new ArrayList<Number>();  // Number "extends" Number (in this context)
List<? extends Number> foo3 = new ArrayList<Integer>(); // Integer extends Number
List<? extends Number> foo3 = new ArrayList<Double>(); // Double extends Number
  1. Reading - Given the above possible assignments, what type of object are you guaranteed to read from List foo3:

    • You can read a Number because any of the lists that could be assigned to foo3 contain a Number or a subclass of Number.
    • You can't read an Integer because foo3 could be pointing at a List<Double>.
    • You can't read a Double because foo3 could be pointing at a List<Integer>.
  2. Writing - Given the above possible assignments, what type of object could you add to List foo3 that would be legal for all the above possible ArrayList assignments:

    • You can't add an Integer because foo3 could be pointing at a List<Double>.
    • You can't add a Double because foo3 could be pointing at a List<Integer>.
    • You can't add a Number because foo3 could be pointing at a List<Integer>.

You can't add any object to List<? extends T> because you can't guarantee what kind of List it is really pointing to, so you can't guarantee that the object is allowed in that List. The only "guarantee" is that you can only read from it and you'll get a T or subclass of T.

super

Now consider List <? super T>.

The wildcard declaration of List<? super Integer> foo3 means that any of these are legal assignments:

List<? super Integer> foo3 = new ArrayList<Integer>();  // Integer is a "superclass" of Integer (in this context)
List<? super Integer> foo3 = new ArrayList<Number>(); // Number is a superclass of Integer
List<? super Integer> foo3 = new ArrayList<Object>(); // Object is a superclass of Integer
  1. Reading - Given the above possible assignments, what type of object are you guaranteed to receive when you read from List foo3:

    • You aren't guaranteed an Integer because foo3 could be pointing at a List<Number> or List<Object>.
    • You aren't guaranteed a Number because foo3 could be pointing at a List<Object>.
    • The only guarantee is that you will get an instance of an Object or subclass of Object (but you don't know what subclass).
  2. Writing - Given the above possible assignments, what type of object could you add to List foo3 that would be legal for all the above possible ArrayList assignments:

    • You can add an Integer because an Integer is allowed in any of above lists.
    • You can add an instance of a subclass of Integer because an instance of a subclass of Integer is allowed in any of the above lists.
    • You can't add a Double because foo3 could be pointing at an ArrayList<Integer>.
    • You can't add a Number because foo3 could be pointing at an ArrayList<Integer>.
    • You can't add an Object because foo3 could be pointing at an ArrayList<Integer>.

PECS

Remember PECS: "Producer Extends, Consumer Super".

  • "Producer Extends" - If you need a List to produce T values (you want to read Ts from the list), you need to declare it with ? extends T, e.g. List<? extends Integer>. But you cannot add to this list.

  • "Consumer Super" - If you need a List to consume T values (you want to write Ts into the list), you need to declare it with ? super T, e.g. List<? super Integer>. But there are no guarantees what type of object you may read from this list.

  • If you need to both read from and write to a list, you need to declare it exactly with no wildcards, e.g. List<Integer>.

Example

Note this example from the Java Generics FAQ. Note how the source list src (the producing list) uses extends, and the destination list dest (the consuming list) uses super:

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

Also see
How can I add to List<? extends Number> data structures?

Java wildcards and generics ? super T and ? extends T

you should have a look at the explanation of PECS principle

What is PECS (Producer Extends Consumer Super)?

In short, when you want to get information from an object, make sure to use extends with the wild card.

And when you want to put information into an object, make sure to use super along with wild card

Use of '? extends ' and '? super ' in Collection generics

The wildcards introduce restrictions in how the collection can be used.

For example, with List<? extends Number>, I can't add new elements to the list. This is because all I know is that the list is some kind of subtype of Number, but I don't know what that actual subtype is (so how could I know what to add?). For example, take the following code:

public void doSomethingWith(List<? extends Number> numbers) {
numbers.add(Integer.valueOf(0)); // Won't compile
}

This won't compile because both of these method calls are legal:

doSomethingWith(new ArrayList<Integer>());
doSomethingWith(new ArrayList<Double>());

What you can do is read elements from the list:

// This will all compile
public void doSomethingWith(List<? extends Number> numbers) {
for (Number number : numbers) {
// Do something with number
}
// OR
Number number = numbers.get(0);
// OR
Number number = numbers.remove(0);
}

Calls to methods like get will return some kind of Number, we know that for a fact because of the ? extends Number, so we can treat it like that for reading purposes.

On the other hand, List<? super Integer> has exactly the opposite result. I can no longer read from the list, but I can write to it. I know that whatever ? is, it will definitely be a super-class of Integer, so concrete types of the list will definitely accept Integer values. For example:

public void doSomethingWith(List<? super Integer> integers) {
integers.add(Integer.valueOf(0));
}

That code is completely legal. However, if you want to read from the list, the only way to do this is to use Object since anything else requires casting (which requires knowing its concrete type):

for (Object obj : integers)
// OR
Object obj = integers.get(0);
// OR
Object obj = integers.remove(0);

What's Really Happening

Here's what's actually happening. When you specify ? extends Number, you're making any method that takes elements as a parameter unusable. In fact, if you try to auto-complete code in Eclipse using Ctrl+Space on a List<? extends Number>, it shows null as the parameters' types in the add methods and the like. Meanwhile, all the methods that return elements are guaranteed to return at least some kind of Number, though you won't know exactly which subclass of Number it might actually be.

When you specify ? super Integer, you're making any method that takes elements as a parameter guarantee that they'll accept Integer values (and sub-classes of Integer as well). This allows you to call methods like add since you know they'll accept Integer types. Meanwhile, all methods that return elements are only guaranteed to return something, but we don't know what, so all the methods that return elements are only guaranteed to return Object.

PECS is an excellent acronym to remember this, it means "Producer Extends, Consumer Supers". This means that if you want your list to give you something, it's a producer, and you should use extends. If you want your list to accept things from you, it's a consumer, so you use super. See this answer for more.

But what if I have a wildcard with no bounds?

It does both! <?> restricts you from calling methods that take the generic type as an argument and causes all the methods that return the generic type to return Object. This is because we have no idea what the type is whatsoever. For example, all of these assignments into a List<?> are legal:

List<?> list;
list = new ArrayList<Integer>();
list = new ArrayList<String>();
list = new ArrayList<MyClass>();

And so on.



Related Topics



Leave a reply



Submit