Java Generics PECS – Producer Extends Consumer Super

Yesterday, I was going through some Java collection APIs, and I found two methods primarily used for adding elements to a collection. They both were using generics syntax for taking method arguments. However, the first method was using <? super T> whereas the second method was using <? extends E>. Why?

1. Syntax

Let’s look at the complete syntax of both methods first.

This method is responsible for adding all members of collection “c” into another collection where this method is called.

boolean addAll(Collection<? extends E> c);

This method is called for adding “elements” to collection “c”.

public static <T> boolean addAll(Collection<? super T> c, T... elements);

Both seem to be doing a simple thing, so why do they both have different syntax? Many of us might wonder. In this post, I am trying to demystify the concept around it, which is primarily called PECS (the term first coined by Joshua Bloch in his book Effective Java).

2. Generics with Wildcards

In my last post related to Java generics, we learned that generics are used for type safety and are invariant by nature. A usecase can be list of Integer i.e. List<Integer>. If you declare a list like List<Integer>, then Java guarantees that it will detect and report you any attempt to insert any non-integer type into the above list.

But many times, we face situations where we have to pass a sub-type or super-type of a class as an argument in a method for specific purposes. In these cases, we have to use concepts like covariance (narrowing a reference) and contra-variance (widening a reference).

3. Understanding ‘Producer Extends‘ or <? extends T>

This is the first part of PECS i.e. PE (Producer extends). To relate it to real-life terms, let’s use an analogy of a basket of fruits (i.e. collection of fruits). When we pick fruit from the basket, then we want to be sure that we are taking out only fruit only and nothing else; so that we can write generic code like this:

Fruit fruit = fruits.get(0);

In the above case, we need to declare the collection of fruits as List<? extends Fruit>. e.g.

class Fruit {
   @Override
   public String toString() {
      return "I am a Fruit !!";
   }
}
 
class Apple extends Fruit {
   @Override
   public String toString() {
      return "I am an Apple !!";
   }
}
 
public class GenericsExamples
{
   public static void main(String[] args)
   {
      //List of apples
      List<Apple> apples = new ArrayList<Apple>();
      apples.add(new Apple());
       
      //We can assign a list of apples to a basket of fruits;
      //because apple is subtype of fruit 
      List<? extends Fruit> basket = apples;
       
      //Here we know that in basket there is nothing but fruit only
      for (Fruit fruit : basket)
      {
         System.out.println(fruit);
      }
       
      //basket.add(new Apple()); //Compile time error
      //basket.add(new Fruit()); //Compile time error
   }
}

Look at the for loop above. It ensures that whatever comes out from the basket is definitely going to be a fruit; so you iterate over it and simply cast it a Fruit. Now in the last two lines, I tried to add an Apple and then a Fruit in the basket, but the compiler didn’t allow me. Why?

The reason is pretty simple if we think about it; the <? extends Fruit> wildcard tells the compiler that we’re dealing with a subtype of the type Fruit, but we cannot know which fruit as there may be multiple subtypes. Since there’s no way to tell, and we need to guarantee type safety (invariance), you won’t be allowed to put anything inside such a structure.

On the other hand, since we know that whichever type it might be, it will be a subtype of Fruit, we can get data out of the structure with the guarantee that it will be a Fruit.

In above example, we are taking elements out of collection “List<? extends Fruit> basket“; so here this basket is actually producing the elements i.e. fruits. In simple words, when you want to ONLY retrieve elements out of a collection, treat it as a producer and use “? extends T>” syntax. “Producer extends” now should make more sense to you.

4. Understanding ‘Consumer Super’ i.e. <? super T>

Now, look at the above usecase in a different way. Let’s assume we are defining a method where we will only add different fruits inside this basket. Just like we saw the method at the start of the post “addAll(Collection<? super T> c, T... elements)“. In such case, the basket is used for storing the elements so it should be called consumer of elements.

Now look at the code example below:

class Fruit {
   @Override
   public String toString() {
      return "I am a Fruit !!";
   }
}
 
class Apple extends Fruit {
   @Override
   public String toString() {
      return "I am an Apple !!";
   }
}
 
class AsianApple extends Apple {
   @Override
   public String toString() {
      return "I am an AsianApple !!";
   }
}
 
public class GenericsExamples
{
   public static void main(String[] args)
   {
      //List of apples
      List<Apple> apples = new ArrayList<Apple>();
      apples.add(new Apple());
       
      //We can assign a list of apples to a basket of apples
      List<? super Apple> basket = apples;
       
      basket.add(new Apple());    //Successful
      basket.add(new AsianApple()); //Successful
      basket.add(new Fruit());    //Compile time error
   }
}

We are able to add apples and even Asian apples inside the basket, but we are not able to add Fruit (super type of Apple) to the basket. Why?

The reason is that basket refers to a List of something that is a supertype of Apple. Again, we cannot know which supertype it is, but we know that Apple and any of its subtypes (which are subtype of Fruit) can be added too without problem (you can always add a subtype in collection of supertype). So, now we can add any type of Apple inside the basket.

What about getting data out of such a type? It turns out that the only thing you can get out of it will be Object instances: since we cannot know which supertype it is, the compiler can only guarantee that it will be a reference to an Object, since Object is the supertype of any Java type.

In above example, we are putting elements inside collection “List<? super Apple> basket“; so here this basket is actually consuming the elements i.e. apples. In simple words, when you want to ONLY add elements inside a collection, treat it as a consumer and use “? super T>” syntax. Now, “Consumer super” also should make more sense to you.

5. Summary

Based on the above reasoning and examples, let’s summarize our learning in bullet points.

  • Use the <? extends T> wildcard if you need to retrieve object of type T from a collection.
  • Use the <? super T> wildcard if you need to put objects of type T in a collection.
  • If you need to satisfy both things, well, don’t use any wildcard. As simple as it is.
  • In short, remember the term PECS. Producer extends Consumer super. Really easy to remember.

That’s all for simple yet complex concepts in generics in Java. Let me know your thoughts via comments.

Happy Learning !!

Comments

Subscribe
Notify of
guest
7 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode