Wildcards and PECS: producer-extends, consumer-super
The three wildcard forms, why you cannot add to ? extends T, and the PECS rule.
The problem with co-assignment
You might expect List<Number> to accept a List<Integer> since Integer extends Number. It does not:
List<Integer> ints = List.of(1, 2, 3);
List<Number> nums = ints; // compile error — not assignable
Generics are invariant by default. This is intentional: if the assignment were allowed you could add a Double through nums and corrupt the Integer list.
Wildcards let you loosen that rigidity:
| Wildcard | Meaning | You can… | You cannot… |
|---|---|---|---|
| ? extends T | upper-bounded | read as T | add (except null) |
| ? super T | lower-bounded | add T subtypes | read only as Object |
| ? | unbounded | read as Object | add anything |
### Upper-bounded: ? extends T — Producer Extends
public static double sum(List<? extends Number> list) {
double total = 0;
for (Number n : list) total += n.doubleValue();
return total;
}
sum(new ArrayList<Integer>()); // OK
sum(new ArrayList<Double>()); // OK
You can read elements as Number. You cannot add (except null) because the compiler doesn't know the actual element type — it could be List<Integer> where a Double would be illegal.
### Lower-bounded: ? super T — Consumer Super
public static void addIntegers(List<? super Integer> sink) {
sink.add(1);
sink.add(2);
sink.add(3);
}
addIntegers(new ArrayList<Number>()); // OK
addIntegers(new ArrayList<Object>()); // OK
You can add Integer values because whatever the actual type is, it's guaranteed to be a supertype of Integer. Reading back gives you only Object — the lower bound gives no useful upper constraint.
### The PECS mnemonic
Producer Extends, Consumer Super — when a data source *produces* values you read, use ? extends T. When a data sink *consumes* values you write, use ? super T.