PECS Rule in Java Generics
Choosing between ? extends and ? super confuses almost everyone at first. Joshua Bloch, author of Effective Java, summed up the choice in four letters: PECS, "Producer Extends, Consumer Super". Ask one question about each parameter: does my method take values out of it (it's a producer) or put values into it (it's a consumer)? The answer picks the wildcard for you.
Producer Extends, Consumer Super
import java.util.ArrayList;
import java.util.List;
public class Pecs {
static <T> void copyInto(List<? extends T> source, List<? super T> target) {
for (T item : source) {
target.add(item);
}
}
public static void main(String[] args) {
List<Integer> marks = List.of(91, 88, 95);
List<Double> fees = List.of(1200.5, 800.0);
List<Number> all = new ArrayList<>();
copyInto(marks, all);
copyInto(fees, all);
System.out.println(all);
List<Object> anything = new ArrayList<>(List.of("Srihitha"));
copyInto(marks, anything);
System.out.println(anything);
}
}[91, 88, 95, 1200.5, 800.0]
[Srihitha, 91, 88, 95]How it works
sourceis a producer: the method only reads from it. So it'sList<? extends T>, which accepts a list ofTor of any subtype.targetis a consumer: the method only adds to it. So it'sList<? super T>, which accepts a list ofTor of any supertype.- With
T = Number, integers and doubles both copy into aList<Number>. WithT = Integer, marks copy into aList<Object>. - With plain
List<T>for both, none of these calls would compile, because each list type would have to match exactly.
The Decision Table
| The method... | The parameter is a... | Declare it as |
|---|---|---|
| Only reads values out of it | Producer | List<? extends T> |
| Only writes values into it | Consumer | List<? super T> |
| Reads and writes | Both | List<T> |
Neither (only size, clear) | Neither | List<?> |
PECS in the JDK
The JDK applies the rule everywhere, which is why its methods accept so many argument types:
| Method | Producer | Consumer |
|---|---|---|
Collections.copy(List<? super T> dest, List<? extends T> src) | src | dest |
Collection.addAll(Collection<? extends E> c) | c | the collection itself |
Collections.max(Collection<? extends T> coll) | coll | |
List.sort(Comparator<? super E> c) | c consumes elements to compare them |
The last row surprises people: a Comparator<Object> can sort a List<String>, because a comparator that can compare any objects can certainly compare strings. The comparator consumes elements, so it's ? super E.
Common Mistakes
Using ? extends for a list you add to. The compiler rejects every add.
Using ? super for a list you read typed values from. You only get Object back.
Using wildcards on a parameter you both read and write. Use a plain type parameter T.
Interview Corner
What is the PECS rule?
PECS stands for "producer extends, consumer super". If a generic parameter only produces values that your method reads, declare it with ? extends T; if it only consumes values that your method writes, declare it with ? super T. A parameter used for both should use the exact type T. Collections.copy(List<? super T> dest, List<? extends T> src) follows the rule.
Practice
- Easy: For each parameter, choose
? extends,? superor plainT: a list you sum; a list you fill with defaults; a list you sort in place. - Harder: Write
static <T> void moveAll(List<? extends T> from, List<? super T> to, Predicate<? super T> keep)that copies only the items the predicate accepts.
Was this tutorial helpful?