Command Palette

Search for a command to run...

PHASE 8Intermediate Java 5+ ~34 min· topic 5 of 6

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 is T or a subtype of T. Safe to read as T.
Lower-bounded wildcard
? super T: an unknown type that is T or a supertype of T. Safe to add T values to.
Unbounded wildcard
? on its own: any type at all. You can read elements only as Object.
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 ? extends or ? 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 of Number[].

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".

terminal
$ javac Main.java # printAll(List<Object>) called with a List<String>
── expected output ──
Main.java:10: error: incompatible types: List<String> cannot be converted to List<Object>
printAll(names);
^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

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.

terminal
$ javac Main.java
── expected output ──
Main.java:7: error: incompatible types: int cannot be converted to CAP#1
nums.add(5);
^
where CAP#1 is a fresh type-variable:
CAP#1 extends Number from capture of ? extends Number
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

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.

terminal
$ javac Main.java
── expected output ──
Main.java:8: error: incompatible types: CAP#1 cannot be converted to Integer
Integer first = sink.get(0);
^
where CAP#1 is a fresh type-variable:
CAP#1 extends Object super: Integer from capture of ? super Integer
1 error

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.

The subtype picturediagram
Rendering diagram…

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.

Main.javawhole filejava
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 extends

06Comparators 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.

terminal
$ javac Main.java
── expected output ──
Main.java:5: error: incompatible types: Object cannot be converted to CAP#1
list.set(i, list.set(j, list.get(i)));
^
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

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.

terminal
$ javac Main.java && java Main
── expected output ──
Exception in thread "main" java.lang.ArrayStoreException: java.lang.String
at Main.main(Main.java:4)

Try it yourself

  1. 1

    Try to add to a producer

    In the sum example, uncomment list.add(1);. Predict the error text, including the CAP#1 line, then compare with the walkthrough. Then try list.add(null);: does it compile?

  2. 2

    Change ? super to ? extends

    In the addSquares example, change the parameter to List<? extends Integer>. Predict which lines now fail: the add inside, the calls in main, or both?

  3. 3

    Remove the capture helper

    In the capture example, inline the helper's body into swap (using Object tmp). Predict the compiler error, then restore the helper.

Code & diagrams

? extends: one sum method for every kind of number New tab
Sign in to run this example in your browser.

Expected output

ints: 6.0
doubles: 4.0
mixed: 110.5
non-null strings: 2
non-null ints: 3
? super: one fill method for several destination types New tab
Sign in to run this example in your browser.

Expected output

ints: [1, 4, 9]
nums: [0.5, 1, 4]
things: [start, 1, 4, 9, 16]
first as Object: start
PECS: copy and sort with a supertype comparator New tab
Sign in to run this example in your browser.

Expected output

[1, 2, 3] [1, 2, 3, x]
heaviest: kesar
sorted: [alphonso, langra, kesar]
Wildcard capture: a public List<?> method with a private helper New tab
Sign in to run this example in your browser.

Expected output

[Meera, Ravi, Asha]
[5, 4, 3, 2, 1]
Arrays are covariant (runtime check), generics are not (compile-time check) New tab
Sign in to run this example in your browser.

Expected output

array: ArrayStoreException
list view first: 1
array still: 1

Break 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);.

terminal
$ javac Main.java
── what you'll see ──
Main.java:7: error: incompatible types: int cannot be converted to CAP#1
nums.add(5);
^
where CAP#1 is a fresh type-variable:
CAP#1 extends Number from capture of ? extends Number
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

Break #2

Read a specific type from a ? super list

Write List<? super Integer> sink = new ArrayList<Number>(); Integer first = sink.get(0);.

terminal
$ javac Main.java
── what you'll see ──
Main.java:8: error: incompatible types: CAP#1 cannot be converted to Integer
Integer first = sink.get(0);
^
where CAP#1 is a fresh type-variable:
CAP#1 extends Object super: Integer from capture of ? super Integer
1 error

Break #3

Pass List<String> where List<Object> is expected

Declare static void printAll(List<Object> items) and call it with a List<String>.

terminal
$ javac Main.java
── what you'll see ──
Main.java:10: error: incompatible types: List<String> cannot be converted to List<Object>
printAll(names);
^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

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 same List<?> variable get different captures, which is why list.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/? super at 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 like List (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 javac can, rarely, hit stack overflows on pathological signatures. Keep signatures simple.

  • ▸

    Wildcards cost nothing at run time: List<? extends Number> erases to List, exactly like List<Number>. They exist only to widen what the compiler accepts. The JDK's Stream API 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. 1

    Topic 8.2 showed generic types are invariant: List<Integer> is not a List<Number>. That keeps code safe but makes it rigid: a method double sum(List<Number> list) can't take a List<Integer>. A wildcard fixes this. List<? extends Number> means "a list of some unknown type that is Number or a subtype", and List<Integer>, List<Double> and List<Number> are all subtypes of it.

  2. 2

    Upper-bounded wildcard ? extends T: you can read elements as T, but you can't add anything (except null). The compiler doesn't know whether the real list is a List<Integer> or a List<Double>, so adding an Integer could corrupt a List<Double>. The error mentions CAP#1, the compiler's name for "the unknown type".

  3. 3

    Lower-bounded wildcard ? super T: you can add T values (and subtypes of T), but reading gives you only Object. List<? super Integer> could be a List<Integer>, List<Number> or List<Object>; all of them accept an Integer, but you can't know what else is already inside.

  4. 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 plain T. 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. 5

    Unbounded wildcard ?: List<?> means "a list of some type I don't care about". You can call size(), clear(), read elements as Object, but not add. It's different from List<Object> (which only accepts an actual List<Object>) and from raw List (which switches checks off). Use List<?> for methods like printAll that only use Object methods.

  6. 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 why swap(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

01

What problem do wildcards solve? Explain with List<Integer> and List<Number>.

02

Explain PECS with Collections.copy as the example.

03

Why can't you add to a List<? extends Number>, and why can you only read Object from a List<? super Integer>?

04

What is the difference between List<?>, List<Object> and raw List?

05

What is wildcard capture, and how does a capture helper work?

Practice

01

Write static double average(Collection<? extends Number> values) and test it with a List<Integer> and a Set<Double> (use TreeSet).

02

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".

03

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 T is fine for private code where you control every caller.

  • ↔

    Use ? extends/? super when a parameter only produces or only consumes; use an exact T when 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 when T appears 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 a List<Number> but is a List<? extends Number>.

  • Done when you can apply PECS to choose ? extends or ? super for 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 raw List apart.

  • 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).