Bounded types and wildcards
extends, super, PECS, and how to read List<? super Integer> without guessing.
The previous lesson ended with a wall: List<Integer> cannot be used where a List<Number> is expected. Wildcards are the door in that wall. They let a method accept "a list of some subtype of Number" or "a list of some supertype of Integer", and the rule for which one you want fits in four letters. This lesson is that rule, what the compiler does with the unknown type it stands for (it has a name, and you have seen it in error messages), and how to read the JDK's most intimidating signatures in one pass.
The problem
static double sum(List<Number> xs) { ... }
sum(List.of(1, 2, 3)); // does not compile: List<Integer> is not List<Number>sum only reads from the list. Reading an Integer where a Number is expected is safe. The signature is too strict.
? extends: a producer you read from
static double sum(List<? extends Number> xs) {
double total = 0;
for (Number n : xs) total += n.doubleValue(); // reading as Number: safe
return total;
}
sum(List.of(1, 2, 3)); // List<Integer>: fine
sum(List.of(1.5, 2.5)); // List<Double>: fineList<? extends Number> means "a list whose element type is Number or some subtype — I do not know which". You can take elements out as Number. You cannot put anything in (except null), because the compiler does not know whether the list is really a List<Integer> or a List<Double>:
xs.add(3); // does not compile: xs might be a List<Double>? super: a consumer you write to
static void fillWithZeros(List<? super Integer> xs, int n) {
for (int i = 0; i < n; i++) xs.add(0); // writing an Integer: safe
}
List<Number> nums = new ArrayList<>();
List<Object> objs = new ArrayList<>();
fillWithZeros(nums, 3); // Number is a supertype of Integer: fine
fillWithZeros(objs, 3); // so is ObjectList<? super Integer> means "a list whose element type is Integer or some supertype". You can put an Integer in, because whatever the real type is, an Integer is one. What you get out is only an Object, because the compiler does not know which supertype it is.
Under the hood: capture, and what the wildcard costs
A wildcard is not a type; it is a constraint on an unknown type. When you use a List<? extends Number>, the compiler performs capture conversion (JLS §5.1.10): it invents a fresh type variable for the unknown, writes it as CAP#1 in its messages, records its bound (CAP#1 extends Number), and type-checks the expression as if the list were a List<CAP#1>. That is the whole explanation of the two rules:
xs.get(0)has typeCAP#1, which is assignable toNumberbecause of the bound. Reading works.xs.add(3)needs anIntegerto be aCAP#1. Nothing is known to be aCAP#1exceptnull. Writing fails, and the error says so:incompatible types: int cannot be converted to CAP#1 where CAP#1 is a fresh type-variable.
For ? super Integer the capture is CAP#1 super Integer: an Integer is assignable to it (write works), and a CAP#1 is only known to be an Object (read gives Object). Every wildcard rule is capture plus one bound.
At run time none of this exists. Erasure turns List<? extends Number> into List, and a read out of it compiles to a checkcast Number, the bound; a read out of List<? super Integer> needs no cast at all. Wildcards are free at run time; their entire cost and benefit is in the compiler.
Bounds can also be recursive. <E extends Enum<E>> says "an enum type, compared with itself", which is what EnumSet.of needs; <T extends Comparable<? super T>> is the next section. These read as constraints on the unknown, exactly like a wildcard, and the compiler solves them by inference at the call site.
PECS
Producer extends, Consumer super.
- If a parameter produces values your code reads →
? extends T. - If a parameter consumes values your code writes →
? super T. - If it does both → an exact
T, no wildcard. - If you neither read as
Tnor writeT→?alone.
The JDK is written this way. Collections.copy(List<? super T> dest, List<? extends T> src) reads from src and writes to dest. Stream.map(Function<? super T, ? extends R>) takes a function that accepts a T (consumer) and returns an R (producer). Once you know PECS, those signatures read as English.
Walkthrough: reading Collectors.toMap in one pass
public static <T, K, U> Collector<T, ?, Map<K, U>> toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper)People give up at this signature. Take it left to right, applying one rule per part:
<T, K, U>: three unknowns, inferred at the call.Tis the stream's element type;KandUare the map's key and value types.Collector<T, ?, Map<K, U>>: a collector that consumesTs and produces aMap<K, U>. The?in the middle is the accumulation type, which callers never see, so it is an unbounded wildcard: "there is one, you do not need to know it".Function<? super T, ? extends K> keyMapper: PECS on both parameters ofFunction. The function consumes aT(so any function that accepts a supertype ofTwill do: aFunction<Object, K>can map aT) and produces aK(so a function returning a subtype ofKwill do). Without the wildcards,toMap(Person::getName, ...)on aStream<Employee>would fail becausePerson::getNameis aFunction<Person, String>, not aFunction<Employee, String>.valueMapper: the same shape forU.- So
employees.stream().collect(toMap(Person::getName, Employee::getSalary))infersT = Employee,K = String,U = Integer, and every wildcard is what lets a method reference declared on a supertype or returning a subtype fit.
The rule for reading any such signature: type parameters first, then the return type, then each parameter as "consumes what, produces what". The wildcards are never the point; they are the slack that makes the point usable.
Reading Comparable<? super T>
static <T extends Comparable<? super T>> T max(Collection<? extends T> xs)T extends Comparable<? super T>: T can be compared to itself or to a supertype of itself. Why not Comparable<T>? Because java.sql.Timestamp extends java.util.Date and implements Comparable<Date>, not Comparable<Timestamp>. The ? super bound accepts it. Collection<? extends T> is PECS: the collection produces elements.
You will not write this signature often. You will read it whenever you open Collections, Comparator or Stream, and being able to parse it in one pass is the skill.
The unbounded wildcard
List<?> is "a list of something". You can read Objects and check size() and isEmpty(); you cannot add. Use it when the element type is genuinely irrelevant — printAll(List<?> xs) — and prefer it to the raw List, which turns checking off entirely. instanceof List<?> is the only reifiable form and the one to write in a type check.
Wildcards are for parameters, not fields or returns
A method parameter with a wildcard makes the method more accepting: good. A return type with a wildcard forces every caller to deal with the wildcard: bad — return List<T>. A field typed List<? extends Foo> can never be added to: almost always wrong. Local variables rarely need them either. Wildcards live in method signatures on the input side.
Wildcard capture
Occasionally you need to name the unknown type — to swap two elements of a List<?>, say. The trick is a helper method with a type parameter:
static void swapFirstTwo(List<?> xs) { swapHelper(xs); }
private static <T> void swapHelper(List<T> xs) {
T tmp = xs.get(0);
xs.set(0, xs.get(1));
xs.set(1, tmp);
}The compiler captures the wildcard as T for the helper call: CAP#1 gets a name you can use, and inside the helper both the read and the write are the same T, so the swap type-checks. This is rare in application code; recognise it when you see it in a library, and recognise the error message that sends you to it: required: CAP#1, found: Object.
Try it yourself
Which lines compile?
List<? extends Number> a = new ArrayList<Integer>();
List<? super Integer> b = new ArrayList<Number>();
Number n1 = a.get(0); // 1
a.add(1); // 2
a.add(null); // 3
b.add(1); // 4
Integer i = b.get(0); // 5
Object o = b.get(0); // 6Answer
1, 3, 4, 6 compile; 2 and 5 do not. a is a List<CAP#1 extends Number>: reading gives a Number (1); writing needs a CAP#1, which only null is (3 yes, 2 no). b is a List<CAP#2 super Integer>: an Integer is a CAP#2 (4); reading gives a CAP#2, known only to be an Object (6 yes, 5 no). Every answer is capture plus the bound.
Fix the signature
static <T> void copyAll(Collection<T> from, Collection<T> to) { for (T t : from) to.add(t); }
List<Integer> ints = List.of(1, 2);
List<Number> nums = new ArrayList<>();
copyAll(ints, nums); // does not compileRewrite copyAll so the call works, and say what T is inferred as.
Answer
static <T> void copyAll(Collection<? extends T> from, Collection<? super T> to). from produces, to consumes: PECS. With the call, inference picks T = Integer (or Number; both satisfy the bounds), List<Integer> fits ? extends T, and List<Number> fits ? super T. This is Collections.copy's signature, and Collections.addAll's.
Read it aloud
public static <T> Comparator<T> comparing(Function<? super T, ? extends Comparable> keyExtractor)(simplified from Comparator.comparing). Say what each wildcard permits, and why Comparator.comparing(Person::getName) works on a Comparator<Employee>.
Answer
? super T: the key extractor may accept any supertype of T, so a function declared on Person can serve a Comparator<Employee>. ? extends Comparable: the key may be any type that is comparable, so String, LocalDate or your own value type all fit. Without the first wildcard, Person::getName would be a Function<Person, String> and not assignable to Function<Employee, String>; with it, the compiler captures CAP#1 super Employee and Person satisfies it.
Misconceptions
- "
List<? extends Number>is a list of any numbers." It is a list of one unknown subtype ofNumber. That is why you cannot add: the compiler does not know which. - "
? supermeans you can read supertypes out." You can only readObjectout.superis for writing; the read side collapses to the top type. - "Wildcards cost something at run time." They erase to the raw type like everything else; the only artefact is a
checkcastto the bound on reads throughextends. - "Wildcards belong everywhere generics do." On method parameters. In fields and return types they push the unknown onto every user.
- "
CAP#1in an error means the compiler is confused." It is the captured wildcard's name. The message is telling you exactly which unknown type the expression needed and did not have.
Going deeper
- JLS §4.5.1 (wildcards) and §5.1.10 (capture conversion), the source of
CAP#1. - JLS §18, type inference, for how
Tis chosen at a call site with wildcards on both sides. - Effective Java, item 31 (use bounded wildcards to increase API flexibility), where PECS was coined.
- The signatures of
Collections.copy,Collections.max,Comparator.comparing,Stream.mapandCollectors.toMap, read with the five-step method above. - Angelika Langer's Java Generics FAQ, the "wildcard capture" section.