skip to content

Unbounded Wildcard

List<?> means a list of some unknown type: you can read elements as Object and add nothing but null. Interviewers ask why the add is rejected, which is really a question about what the compiler does and does not know.

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

questions

5

Why can you not add elements (other than null) to a List<?>, but you can still read from it?

level: middleimportance: must knowfreq 62%

answer

  1. ? captured as one unknown type CAP#1
  2. add needs a value provable as CAP#1 - none exists
  3. null is a member of every reference type
  4. get returns CAP#1, always an Object
  5. Producer-only (PECS: extends = producer)

basics

~20 s

The compiler doesn't know the real element type behind ?, so it can't be sure any value you add fits - except null, which fits every type. Reading is fine because whatever comes out is at least an Object.

solid answer

~40 s

With List<?> the element type is a single, unknown, specific type - call it CAP#1 (the compiler's 'capture' of the wildcard). To allow add(x), the compiler must prove x is of type CAP#1. Since CAP#1 is unknown, it can prove this for no value at all - except null, which is assignable to every reference type. So every add is rejected except add(null). Reading is the mirror image: get() returns CAP#1, and whatever CAP#1 is, it is guaranteed to be an Object. So you can always read into an Object variable. This is the get/put asymmetry: an unbounded (or upper-bounded) wildcard is effectively a producer you can read from but not write to. If you need to write, you need a concrete type parameter or a lower-bounded wildcard like List<? super T>.

go deeper

for a junior

Knows you can read List<?> as Object and can only add null.

for a middle

Explains the restriction via 'one unknown type' and that null is a member of every reference type.

for a senior

Frames it with wildcard capture (CAP#1) and the producer/consumer (PECS) model, and knows the write-enabling alternative is ? super T.

for a principal

Reasons about soundness guarantees the rule enforces and applies the producer/consumer distinction to API design across a codebase.

## The core idea: one unknown type, not 'any type' `List<?>` does **not** mean 'a list that can hold any type'. It means 'a list whose element type is **one specific type that we don't know**'. The compiler internally gives that unknown type a fresh name - this is called **wildcard capture**, often shown in errors as something like `CAP#1`. ## Why `add` is (almost) forbidden Consider `void f(List<?> list)`. Inside, suppose you try `list.add("hello")`. The actual list passed in might be: ``` f(new ArrayList<Integer>()); // ? is Integer ``` Now `list.add("hello")` would put a `String` into a `List<Integer>` - heap corruption. The compiler has no way to know which type was passed, so to stay **sound** (never allow a bad value in), it rejects `add` for *every* concrete value. The signature of `add` becomes effectively `add(CAP#1 e)`, and you have no value you can prove is a `CAP#1`... ## ...except `null` `null` is the one value that is a legal member of **every** reference type. `CAP#1`, whatever it is, is a reference type, so `null` is always a valid `CAP#1`. Therefore `list.add(null)` compiles. It is the single exception. ## Why reading always works `E get(int i)` becomes effectively `CAP#1 get(int i)`. You don't know what `CAP#1` is, but you know one thing for certain: it is a reference type, so it **is an Object**. Hence: ``` Object o = list.get(0); // always OK ``` You simply cannot assign the result to anything more specific than `Object`, because you can't prove it is, say, a `String`. ## The mental model: producer vs consumer An unbounded wildcard (and an upper-bounded one, `? extends T`) makes the collection a **producer**: you can take things *out* (read) but not put things *in* (write). This is half of the **PECS** rule - *Producer Extends, Consumer Super*. To consume (write into) a collection you need `? super T` or a concrete type. `List<?>` is the most extreme producer-only form: you can only read elements as `Object`. ## Practical consequence Methods that take `List<?>` advertise 'I will not modify your list's contents'. That makes them safe to call with a list of any element type, which is exactly the flexibility the wildcard buys.

  • What is wildcard capture?
    It is the compiler giving the unknown wildcard type a fresh, named type variable (e.g. CAP#1) so it can reason about it; it appears in compiler error messages and underlies the add/get rules.
  • How does this relate to ? extends T versus ? super T?
    ? extends T and unbounded ? are producers (read-only, by PECS 'extends'); ? super T is a consumer you can write T into. The unbounded wildcard behaves like the read-only producer side.

saying these in an interview costs you the question

  • Saying ? means 'any type', so anything can be added - it means one unknown type
  • Believing you cannot read at all from List<?> - you can, as Object
  • Forgetting that null is the allowed exception to the add rule
  • Confusing this with List<? super T>, which DOES allow writing T

context

open as a page

What does the unbounded wildcard List<?> mean, and how is it different from List<Object> and the raw type List?

level: middleimportance: must knowfreq 70%

basics

~20 s

List<?> means 'a list of some unknown but specific type'. List<Object> is a list that explicitly holds any object, and raw List has no type info at all. List<?> is type-safe; raw List is not.

open as a page

Is List<?> just a fancy way of writing the raw type List? Explain the practical difference.

level: juniorimportance: should knowfreq 40%

basics

~20 s

No. The raw List turns off generic checking and lets you do unsafe things with warnings. List<?> keeps the type checks on: you read as Object and can only add null, so the compiler still protects you.

open as a page

When would you choose List<?> for a method parameter instead of a generic type parameter like <T> List<T>?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Use List<?> when the method only cares that it has a list and never needs to name the element type - for example to check its size or print it. Use a type parameter <T> when the element type must appear in more than one place, like returning an element of that same type.

open as a page

At runtime, what does List<?> look like, and how does it relate to type erasure and operations like instanceof?

level: seniorimportance: nice to knowfreq 30%

basics

~10 s

At runtime Java erases generic type information, so List<String> and List<Integer> are both just List. Because of that, the only generic instanceof check Java allows is the unbounded wildcard form: obj instanceof List<?>.

open as a page