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 !!
In first example, we are using extends keyword. since we extends for subtyping so why can’t we put apple which is a subtype of fruits ?.
Because a reference to List could point to a List or a List or a List or any number of fruit types.
In this case we know it’s a List, so adding an apple would be fine, but in general we don’t know what the actual type of the list is, just that it contains some kind of Fruit. If you were able to add a Banana to a List, then if the actual list was of type List then your list of AsianApples would suddenly contain a Banana! You would have circumvented the type system.
Because a reference to
could point to a
or a
or a
or any number of fruit types.
In this case we know it’s a
, so adding an apple would be fine, but in general we don’t know what the actual type of the list is, just that it contains some kind of Fruit. If you were able to add a Banana to a
, then if the actual list was of type
then your list of AsianApples would suddenly contain a Banana! You would have circumvented the type system.
nice article , easy examples , clears concept ,
Thanks
Hi Lokesh,
I have a situation in which I need to make sure that only SubType elements are going to be accepted in a List.
Say I have ClassA which is getting extended by ClassB,ClassC and I want to declare a List, How I can achieve that ?
Thanks in advance.
Hi Lokesh, thank you for the post, I wonder how would one write non-generic code for the same above discussed scenario. I feel it would be even more helpful to understand the distinction if you could explain the same examples with non-generic code.
Generics in java are present to provide type safety during compile time i.e. writing the code. Once you have compiled the code, there is no extra bit added to bytecode related to generics, and it’s completely equal to raw code. So if you are not using generics, then every collection is going to be equal only ArrayList of Objects, where you can put anything and get anything out of it. It’s coder’s responsibility to write code in such a way that no ClassCastExceptions are thrown at runtime. It means you have to take care of every collection in application, and remember in which collection what are the value type.