Java Generics: Producer Extends Consumer Super

Amitosh Swain Mahapatra
2 min readMay 1, 2020

--

I do quite a lot of Java as part of the work I do at Gojek and one of the most helpful, but at times confusing part of the language is generics. Here I will be mentioning an interesting concept I recently came across — PECS or “Producer Extends, Consumer Super”

Photo by Samuel Sianipar on Unsplash

Generics in Java are invariant by default and are applied at compile-time. Polymorphic type arguments do not imply polymorphism in generic types, i.e., You cannot assign List<String> to List<CharSequence>.

However, with extends and super, you achieve “covariance” and “contra-variance” that aids in designing type-safe generic code aware of inheritance and polymorphism.

This is useful in defining methods that accept collection types.

  • Use ? extends T when the collection is “producing” items that you utilize. Like, iterating over all items of the collection. You only care if the list items are compatible with T (i.e., subclasses of T). This is covariance — narrowing of scope.
  • Use super when the collection is “consuming” items. A copyToList method that copies current results into a list allows you to store objects of type T. This is contra-variance — widening of scope.

Examples

? extends T — covariance

You need to apply a List<Rule> over a StateMachine instance Here the List<Rule> is a “producer.”

interface Rule {void apply(StateMachine m);}class FireWallRule implements Rule {// ...}class StateMachine {
// ...
public void apply(List<? extends Rule> rules) {
// ...
}
}
class Main {
public static void main(String[] args) {
var m = new StateMachine();
var rules = List.of(
new FireWallRule(),
// ...
);
m.apply(rules);
}
}

This allows you to pass a List of any objects which are implementations of Rule

? super T — contra-variance

You need to store some instances of FetchResult, a sub-type of Result into a List. Here the List is a “consumer.”

interface Result {
// ...
}
interface FetchResult extends Result {
// ...
}
class Fetcher {
putResult(List<? super FetchResult> output) {
// ...
}
}
class Main {
public static void main(String[] args) {
var result = new ArrayList<Result>();
var fetcher = new Fetcher();
// ...
fetcher.getResult(result);
}
}

This allows to pass any List that can store a FetchResult.

However, such code wont work:

List<? extends CharSequence> list = new ArrayList<>();// ...list.add("hello world"); // error here, cannot add

However, a normal List<CharSequence> will allow it.

--

--

Amitosh Swain Mahapatra
Amitosh Swain Mahapatra

Written by Amitosh Swain Mahapatra

Computer Whisperer. Open-source contributor. Find me at https://amitosh.in/

Responses (1)