Java 13: Wildcards & PECS
easy⏱ 5 mincoursejava
Invariance by default
List<Integer> is not a subtype of List<Number>, even though Integer extends Number. Generics are invariant. The reason: if the assignment were legal, you could add(3.14) to a List<Number> reference that actually points to a List<Integer> — runtime heap pollution. Wildcards are Java's way to relax invariance safely.
? extends T — producer
List<? extends Number> list means 'a list of some specific subtype of Number — I don't know which'. You can get() items as Number, but you can't add() anything (except null) — the compiler doesn't know the exact type. Use this when you only read.
List<? extends Number> nums = List.of(1, 2, 3); // actually List<Integer>
Number n = nums.get(0); // OK — everything is a Number
// nums.add(4); // compile error — can't add to unknown subtype
? super T — consumer
List<? super Integer> list means 'a list of some specific supertype of Integer'. You can add(Integer) safely (it fits any supertype), but get() only returns Object. Use this when you only write.
List<? super Integer> sink = new ArrayList<Number>();
sink.add(42); // OK — Integer fits Number or Object
Object x = sink.get(0); // best you can say about the element
Implement copy<T>(src, dest) using PECS
Signature: copy<T>(src: T[] as 'producer', dest: T[] as 'consumer'). Annotate intent with comments showing where ? extends T vs ? super T would apply. Log a run with src = Integer[] and dest = Number[] — in Java this requires copy(List<? extends Integer>, List<? super Integer>).
Collections.copy uses PECS
public static <T> void copy(List<? super T> dest, List<? extends T> src) — the classic PECS example from java.util.Collections. Memorize that signature and you've memorized PECS.
Quiz: Why can't you add to ? extends
List<? extends Number> l = new ArrayList<Integer>(); — why does l.add(5) fail to compile? Because the compiler only knows it's some subtype of Number — possibly Double. Adding an Integer could violate that. Safety over convenience.