Topic 8.5
Wildcards and PECS
In one line
A wildcard ? stands for "some type I don't name". List<? extends Number> is a list you can safely read Numbers from, and List<? super Integer> is one you can safely add Integers to. PECS, "Producer Extends, Consumer Super", tells you which to use.
Think of it like this
A fruit basket and a recycling bin. If someone hands you "a basket of some kind of fruit" (maybe apples, maybe mangoes), you can safely take things out and know each one is a fruit, but you can't put a banana in, because it might be the apple basket. A bin labelled "anything that is paper or more general" is the opposite: you can always throw paper in, but when you reach in, all you know is that you'll get "some stuff". ? extends is the basket you take from; ? super is the bin you put into.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Wildcard
- The
?in a type argument, meaning "some specific type that isn't named here". - Upper-bounded wildcard
? extends T: an unknown type that isTor a subtype ofT. Safe to read asT.- Lower-bounded wildcard
? super T: an unknown type that isTor a supertype ofT. Safe to addTvalues to.- Unbounded wildcard
?on its own: any type at all. You can read elements only asObject.- Producer
- A parameter your method only takes values out of, like the source list in a copy.
- Consumer
- A parameter your method only puts values into, like the destination list in a copy.
- PECS
- Producer Extends, Consumer Super: the rule for choosing
? extendsor? super. - Wildcard capture
- The compiler giving the unknown type behind a
?a temporary internal name (CAP#1) so it can check each use. - Covariant
- Varying in the same direction as the element type: arrays are covariant, so
Integer[]is a subtype ofNumber[].
Step by step
01The problem: invariance is too strict
You write static double sum(List<Number> list). It works for a List<Number>, but calling it with a List<Integer> fails, because List<Integer> isn't a List<Number> (Topic 8.2).
You don't want to write sumInts, sumDoubles and sumLongs. You want "a list of any kind of number".
02? extends: read freely, never add
static double sum(List<? extends Number> list) accepts List<Integer>, List<Double>, List<Number>. Inside, for (Number n : list) works, because whatever the unknown type is, it's a Number.
Adding fails. The compiler only knows the list holds some CAP#1 extends Number. An Integer isn't necessarily a CAP#1 (the list might be a List<Double>), so add(5) is rejected. Only null fits every type.
03? super: add freely, read only Object
static void fill(List<? super Integer> list) accepts List<Integer>, List<Number> and List<Object>. Every one of them can hold an Integer, so list.add(5) is safe.
Reading is the weak side: the list might be a List<Object> full of strings, so get returns only Object.
04The subtype picture
Wildcards create the subtype relationships invariance took away, but in a safe form. List<Integer> is a subtype of List<? extends Integer>, which is a subtype of List<? extends Number>, which is a subtype of List<?>. On the other side, List<Number> is a subtype of List<? super Integer>.
So an API that takes List<? extends Number> accepts the widest possible range of callers while staying type-safe.
05PECS in one method: copy
copy(dest, src) reads from src (a producer of Ts) and writes into dest (a consumer of Ts). PECS gives static <T> void copy(List<? super T> dest, List<? extends T> src).
Now you can copy a List<Integer> into a List<Number> or a List<Object>. With plain List<T> on both sides, both lists would have to have exactly the same element type. This is literally the signature of java.util.Collections.copy.
static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (T item : src) { // src produces T values
dest.add(item); // dest consumes T values
}
}
List<Integer> ints = List.of(1, 2, 3);
List<Number> nums = new ArrayList<>();
copy(nums, ints); // T = Integer: Number is a super, Integer is an extends06Comparators are consumers too
A Comparator<T> takes T values in (compare(T a, T b)), so it's a consumer: sort(List<T> list, Comparator<? super T> c). That lets you sort a List<Apple> with a Comparator<Fruit> that compares weights.
That's the same idea as Topic 8.4's Comparable<? super T>: compareTo consumes a T.
07Wildcard capture and the helper method
static void swap(List<?> list, int i, int j) is the nicest signature for callers, but the body can't be written directly: list.get(i) returns Object, and list.set(j, ...) needs the captured CAP#1, which Object isn't.
Call a private generic helper: swapHelper(list, i, j) with <T> void swapHelper(List<T> list, ...). Inference binds T to the captured type, and inside the helper the element type has a name. Collections.swap and Collections.reverse are written exactly like this.
08Why arrays didn't need wildcards (and why that's worse)
Arrays are covariant: Integer[] is a subtype of Object[]. So Object[] arr = new Integer[2]; arr[0] = "hello"; compiles. The JVM must check every array store at run time and throws ArrayStoreException.
Generics chose invariance plus wildcards, so the same mistake is a compile error. The cost is having to learn ? extends and ? super; the reward is no runtime store check and no surprise exception.
Try it yourself
- 1
Try to add to a producer
In the
sumexample, uncommentlist.add(1);. Predict the error text, including theCAP#1line, then compare with the walkthrough. Then trylist.add(null);: does it compile? - 2
Change ? super to ? extends
In the
addSquaresexample, change the parameter toList<? extends Integer>. Predict which lines now fail: theaddinside, the calls inmain, or both? - 3
Remove the capture helper
In the capture example, inline the helper's body into
swap(usingObject tmp). Predict the compiler error, then restore the helper.
Code & diagrams
Expected output
ints: 6.0
doubles: 4.0
mixed: 110.5
non-null strings: 2
non-null ints: 3Expected output
ints: [1, 4, 9]
nums: [0.5, 1, 4]
things: [start, 1, 4, 9, 16]
first as Object: startExpected output
[1, 2, 3] [1, 2, 3, x]
heaviest: kesar
sorted: [alphonso, langra, kesar]Expected output
[Meera, Ravi, Asha]
[5, 4, 3, 2, 1]Expected output
array: ArrayStoreException
list view first: 1
array still: 1Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Add to a ? extends list
Write List<? extends Number> nums = new ArrayList<Integer>(); nums.add(5);.
Break #2
Read a specific type from a ? super list
Write List<? super Integer> sink = new ArrayList<Number>(); Integer first = sink.get(0);.
Break #3
Pass List<String> where List<Object> is expected
Declare static void printAll(List<Object> items) and call it with a List<String>.
Myth vs fact
Myth
List<?> is the same as List<Object>.
Fact
List<Object> accepts only a real List<Object>, and you can add anything to it. List<?> accepts a list of any type, and you can't add to it except null.
Myth
List<?> is the same as a raw List.
Fact
A raw List disables type checks, so you can add anything with a warning and corrupt the list. List<?> keeps full checking and simply forbids unsafe adds.
Myth
A List<? extends Number> is completely read-only.
Fact
You can still remove, clear, add null, and change it through other references. It's only adding typed elements through that reference that's blocked. For real immutability use List.copyOf or List.of.
Myth
Wildcards belong in return types too.
Fact
A wildcard return type forces every caller to deal with an unknown type. Bloch's rule: use wildcards in parameters for flexibility, return concrete types.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Wildcard capture is defined in JLS 5.1.10: every time an expression of a wildcard type is evaluated, the compiler converts it to a fresh type variable (
CAP#n) with the wildcard's bounds. Two reads of the sameList<?>variable get different captures, which is whylist.set(i, list.get(j))can't type-check even though it would be safe at run time. - ▸
Java's generics use use-site variance: the caller or method parameter chooses
? extends/? superat each use. Kotlin and C# use declaration-site variance (out T,in T) on the type itself. Java 5 picked use-site so existing classes likeList(which both reads and writes) didn't need to be split. - ▸
The wildcard type system is powerful enough to be undecidable in corner cases: Radu Grigore's paper "Java Generics are Turing Complete" (POPL 2017) showed that subtype checking with wildcards can encode any computation. In practice it means
javaccan, rarely, hit stack overflows on pathological signatures. Keep signatures simple. - ▸
Wildcards cost nothing at run time:
List<? extends Number>erases toList, exactly likeList<Number>. They exist only to widen what the compiler accepts. The JDK'sStreamAPI uses them heavily:map(Function<? super T, ? extends R> mapper)follows PECS for both the input and the output of the function (Phase 10).
Remember this
- 1
Topic 8.2 showed generic types are invariant:
List<Integer>is not aList<Number>. That keeps code safe but makes it rigid: a methoddouble sum(List<Number> list)can't take aList<Integer>. A wildcard fixes this.List<? extends Number>means "a list of some unknown type that isNumberor a subtype", andList<Integer>,List<Double>andList<Number>are all subtypes of it. - 2
Upper-bounded wildcard
? extends T: you can read elements asT, but you can't add anything (exceptnull). The compiler doesn't know whether the real list is aList<Integer>or aList<Double>, so adding anIntegercould corrupt aList<Double>. The error mentionsCAP#1, the compiler's name for "the unknown type". - 3
Lower-bounded wildcard
? super T: you can addTvalues (and subtypes ofT), but reading gives you onlyObject.List<? super Integer>could be aList<Integer>,List<Number>orList<Object>; all of them accept anInteger, but you can't know what else is already inside. - 4
PECS, from Joshua Bloch's *Effective Java*: 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 plainT. The JDK follows it everywhere:Collections.copy(List<? super T> dest, List<? extends T> src),Collection.addAll(Collection<? extends E> c),List.sort(Comparator<? super E> c). - 5
Unbounded wildcard
?:List<?>means "a list of some type I don't care about". You can callsize(),clear(), read elements asObject, but not add. It's different fromList<Object>(which only accepts an actualList<Object>) and from rawList(which switches checks off). UseList<?>for methods likeprintAllthat only useObjectmethods. - 6
Each time an expression of wildcard type is used, the compiler invents a fresh, unnamed type for it, called wildcard capture (
CAP#1). You can't write that type, which is whyswap(List<?> list, ...)can't put back an element it just read. The fix is a private generic capture helper,<T> void swapHelper(List<T> list, ...), which gives the captured type a name. Rule of thumb: use wildcards in parameters of public methods, never in return types, where they push the confusion onto every caller.
Explain it without notes
What problem do wildcards solve? Explain with List<Integer> and List<Number>.
Explain PECS with Collections.copy as the example.
Why can't you add to a List<? extends Number>, and why can you only read Object from a List<? super Integer>?
What is the difference between List<?>, List<Object> and raw List?
What is wildcard capture, and how does a capture helper work?
Practice
Write static double average(Collection<? extends Number> values) and test it with a List<Integer> and a Set<Double> (use TreeSet).
Write static void addRange(List<? super Integer> out, int from, int to) and use it to fill both a List<Integer> and a List<Object> that already contains "start".
Write static <T> void moveAll(List<? extends T> from, List<? super T> to) that copies all elements and prints the count moved. Move List.of(1.5, 2.5) into a List<Number>.
Trade-offs
- ↔
Wildcard parameters make an API accept many more callers, at the price of harder-to-read signatures. Use them in public, reusable methods; plain
Tis fine for private code where you control every caller. - ↔
Use
? extends/? superwhen a parameter only produces or only consumes; use an exactTwhen the method both reads and writes the same collection. A type parameter (<T> void f(List<T>)) and a wildcard (void f(List<?>)) are often interchangeable; prefer the wildcard whenTappears only once. - ↔
Never return wildcard types: callers then hold a
List<? extends X>they can't add to. Return the concrete type and keep the flexibility on the input side.
Done when you can
Done when you can explain why
List<Integer>isn't aList<Number>but is aList<? extends Number>.Done when you can apply PECS to choose
? extendsor? superfor a parameter.Done when you can say exactly what you may read and add for each wildcard kind.
Done when you can tell
List<?>,List<Object>and rawListapart.Done when you can write a capture helper for a
List<?>method.Done when you can read JDK signatures like
sort(Comparator<? super E> c).