PECS in Java
Producer Extends, Consumer Super.
? extends, and which gets ? super? Get it wrong and half your callers can't use it. Four letters fix this forever.PECS
Producer Extends, Consumer Super. If a parameter produces values your method reads, use **? extends T. If it consumes values your method writes, use ? super T. If it does both, use plain T**.
Producer → extends
drawAll only reads shapes from the list, so the list is a producer: List<? extends Shape>. Now callers can pass a List<Circle> and a List<Shape>.
static void drawAll(
List<? extends Shape> shapes) {
for (Shape s : shapes) s.draw();
}
drawAll(circles); // List<Circle> ✓Why not plain List<Shape>?
Circle extends Shape. Why wouldn't drawAll(List<Shape> shapes) accept a List<Circle>?
Think about it, then reveal the answer
Because generics are invariant: a List<Circle> is not a List<Shape>. The wildcard ? extends Shape is what opens the door for subtypes.
Consumer → super
A destination list you write into is a consumer: List<? super T>. The JDK's own Collections.copy follows PECS exactly: you can copy Integers into a List<Number> or List<Object>.
static <T> void copy(List<? super T> dest,
List<? extends T> src)Your turn
What does this print?
static <T> void moveAll(List<? extends T> src,
List<? super T> dst) {
dst.addAll(src);
}
void main() {
List<Object> sink = new ArrayList<>();
sink.add(1);
moveAll(List.of("b"), sink);
System.out.println(sink);
}[1, b][b]Compile error
Show the answer
src produces Strings (? extends T), dst consumes them (? super T). With T = String, a List<Object> is a perfectly good consumer: [1, b].
Comparators are consumers
A comparator consumes the elements it compares. That's why List.sort takes a **Comparator<? super E>**: anything that can compare Numbers can certainly compare Integers.
Comparator<Number> byValue =
Comparator.comparingDouble(
Number::doubleValue);
List<Integer> ints = new ArrayList<>();
ints.sort(byValue); // ✓ ? super IntegerReading AND writing? No wildcard
If a method both reads from and writes to the same list, neither wildcard works: ? extends blocks the writes, ? super makes reads return Object. Use the exact type **T**.
static <T> void swapEnds(List<T> xs) {
T first = xs.get(0); // read
xs.set(0, xs.get(xs.size() - 1));
xs.set(xs.size() - 1, first); // write
}Designing APIs
PECS is what makes JDK methods like Collections.copy, addAll, List.sort and Collections.max so flexible. When you design a library method, applying PECS means callers never have to copy their lists just to fit your signature. It's a favourite interview question, too.
Key takeaways
- Read from it (producer) → ? extends T
- Write into it (consumer) → ? super T
- Both read and write → exact type T
- Comparators are consumers: Comparator<? super T>
💡 A producer is a tap you drink from; a consumer is a sink you pour into.
The PECS mnemonic was coined by Joshua Bloch in Effective Java (2nd edition, 2008). Bloch also designed the Collections Framework that uses it everywhere.
Practice questions
drawAll(...) only READS shapes from a list and draws them. Which parameter type lets callers pass a List<Circle> as well as a List<Shape>?
- List<Shape>
- List<? extends Shape>
- List<? super Shape>
- List<Object>
Check your answer
List<? extends Shape>. The list is a producer of Shapes, so ? extends Shape. A plain List<Shape> would reject a List<Circle> because generics are invariant.
Complete the JDK's Collections.copy signature.
static <T> void copy(List<___> dest,
List<? extends T> src)- ? super T
- ? extends T
- T
- ?
Check your answer
? super T. dest is written to, so it's a consumer: ? super T. That allows copying a List<Integer> into a List<Number> or List<Object>.