skip to content

Wildcard Capture and Read/Write Restrictions

The compiler captures an unknown wildcard into a fresh type variable, which is why a private helper method can perform an operation the wildcard-typed caller cannot. The capture-helper idiom is the practical takeaway interviewers look for.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

Why can you read from a List<? extends Number> but not add elements to it (other than null), and what is the inverse rule for List<? super Integer>?

level: middleimportance: must knowfreq 70%

answer

  1. PECS: Producer Extends, Consumer Super
  2. extends = read-safe, write-blocked (except null)
  3. super = write-safe (T & subtypes), read gives Object
  4. Invariance is the reason wildcards exist
  5. Unknown exact type -> can't prove add is legal

basics

~20 s

With '? extends Number' the exact type is unknown, so the compiler can't be sure a value you add is allowed, but every element is at least a Number so reading is safe. With '? super Integer' the reverse holds: you can add Integers, but reads only give you Object.

solid answer

~40 s

A wildcard stands for some single but unknown type. With List<? extends Number> the list could really be List<Integer> or List<Double>, so the compiler cannot let you add anything (a Double would be illegal if it is actually List<Integer>); the only value provably assignable to every possibility is null. Reading is safe because whatever the element type is, it is guaranteed to be a Number. List<? super Integer> is the mirror: it could be List<Integer>, List<Number>, or List<Object>, so adding an Integer (or any subtype) is always safe, but reading gives back only Object because that is the one type known to be a supertype of all possibilities. This is the basis of PECS: Producer Extends, Consumer Super.

code

java · 9 lines
java
List<? extends Number> producer = new ArrayList<Integer>();
Number n = producer.get(0);   // OK: element is at least Number
// producer.add(4);           // compile error: unknown exact type
producer.add(null);          // OK: null fits any element type

List<? super Integer> consumer = new ArrayList<Number>();
consumer.add(42);             // OK: Integer fits Integer/Number/Object
Object o = consumer.get(0);   // OK, but only as Object
// Integer i = consumer.get(0); // compile error

go deeper

for a junior

Knows you can read from '? extends' and write to '? super', and can recite PECS even if shaky on why.

for a middle

Explains both restrictions in terms of the unknown exact type and gives the null exception; applies PECS to a copy method.

for a senior

Connects the rules to invariance and type soundness, writes correct bounded signatures, and reasons about the get-returns-Object case for super.

for a principal

Frames it as variance in the type system, contrasts with declaration-site variance in other languages (Kotlin/Scala), and weighs API ergonomics of exposing wildcards.

## Background **Generics** parameterize a type: `List<String>` is a list of `String`. The thing in angle brackets is the *type argument*. Generics are **invariant**: even though `Integer` is a subtype of `Number`, `List<Integer>` is NOT a subtype of `List<Number>`. If it were, you could store a `Double` through a `List<Number>` reference that actually points at a `List<Integer>`, corrupting it. So the compiler forbids that assignment. A **wildcard** `?` relaxes invariance safely. `List<? extends Number>` means "a List of *some* unknown type that is Number or a subtype of Number." `List<? super Integer>` means "a List of some unknown type that is Integer or a *supertype* of Integer." ## Why you can't add to `List<? extends Number>` The reference could point at a `List<Integer>`, a `List<Double>`, or a `List<Number>` — the compiler does not know which. To call `list.add(x)`, the compiler must prove `x` is assignable to the real element type. Since that type is unknown (only known to be "some subtype of Number"), no concrete value qualifies. If you tried `list.add(new Double(1))` and the list were really `List<Integer>`, you would corrupt it. The *only* value assignable to every possible subtype is `null`, so `list.add(null)` compiles. ## Why you CAN read it as Number Whatever the real element type is, it is guaranteed to be Number-or-below. So `Number n = list.get(0);` is always type-safe — the element is at least a Number. ## The inverse: `List<? super Integer>` Now the reference could point at `List<Integer>`, `List<Number>`, or `List<Object>` — some unknown supertype of Integer. - **Adding** is safe: an `Integer` is assignable to Integer, Number, and Object alike. So `list.add(42)` compiles. - **Reading** loses information: the element could be Integer, Number, or Object, so the only type the compiler can guarantee for a read is `Object`. `Object o = list.get(0);` works; `Integer i = list.get(0);` does not. ## PECS Joshua Bloch's mnemonic: **Producer Extends, Consumer Super**. If a parameterized type *produces* values you consume (you read from it), use `extends`. If it *consumes* values you supply (you write to it), use `super`. A method that copies typically takes a `? extends T` source (producer) and a `? super T` destination (consumer). ## Summary table | Type | add(x) | get() returns | |---|---|---| | `List<? extends Number>` | only `null` | `Number` | | `List<? super Integer>` | `Integer` and subtypes | `Object` | | `List<?>` (unbounded) | only `null` | `Object` |

  • What is the one value you can always add to a List<? extends Number>?
    null — it is assignable to every possible element type, so it is the only add that the compiler can prove safe.
  • Write the signature of Collections.copy using PECS.
    static <T> void copy(List<? super T> dest, List<? extends T> src) — src produces (extends), dest consumes (super).

saying these in an interview costs you the question

  • Saying you can add any Number to List<? extends Number>
  • Claiming List<Integer> is a subtype of List<Number>
  • Thinking '? super Integer' lets you read back an Integer
  • Confusing extends/super directions of PECS
  • Forgetting that null is the one value you can always add

context

open as a page

What is the difference between List<?>, the raw type List, and List<Object>, especially regarding type safety and what you can put in them?

level: middleimportance: should knowfreq 50%

basics

~20 s

List<?> is a list of some unknown type and is type-safe — you can't add anything but null. Raw List has no type checking and lets you add anything (unsafe, legacy). List<Object> is a list that explicitly holds any Object and lets you add anything safely.

open as a page

How does the capture-helper (capture-conversion) idiom let you implement a method like swap on a List<?>, and why is the helper necessary?

level: seniorimportance: should knowfreq 40%

basics

~20 s

You write a public method taking List<?> and have it call a private generic helper method <T> that takes List<T>. Passing the wildcard list to the helper makes the compiler capture the unknown type as T, so inside the helper you can read and write elements consistently.

open as a page

What is wildcard capture in Java generics, and when does the compiler perform it?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Wildcard capture is when the compiler replaces a '?' with a fresh, made-up type variable so it can reason about it as a single concrete type. It happens when a method takes a wildcard-typed argument and binds it to a real type parameter.

open as a page

You see a compile error like 'method set in interface List cannot be applied; required: CAP#1, found: Object'. What does it mean and how do you fix it?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

It means you tried to write a plain Object into a list whose element type is an unknown captured wildcard (CAP#1). The compiler can't prove the Object is the right type. Fix it by routing through a generic helper method so the unknown type gets a name.

open as a page