🧪 Generics · Intermediate

Wildcards in Java

?, ? extends T (read), ? super T (write).

🧩 The mysteryThe list really holds Doubles. You try to add a Double. The compiler says no. Welcome to wildcards, where Java plays it safe with what it can't see.

? means "some type I don't know"

**List<?> is a list of one specific type that's unknown here**, not a mix of anything. You can read elements as Object, but you can't add (except null). Wildcards appear in variable and parameter types, never after new.

List<?> xs = List.of("a", "b");
Object o = xs.get(0);       // ✓ read
// xs.add("c");             // ✗
// new ArrayList<?>()       // ✗ not allowed

? extends T: safe to read

**List<? extends Number>** could be a List<Integer>, a List<Double>... Whatever it is, every element is a Number, so reading as Number is safe. That's why one method can accept both.

static double total(
        List<? extends Number> xs) {
    double t = 0;
    for (Number n : xs) t += n.doubleValue();
    return t;
}
total(List.of(1, 2));   // List<Integer> ✓
total(List.of(0.5));    // List<Double>  ✓
🔮 Predict it

Your turn

The list really is an ArrayList<Double>. What happens?

List<? extends Number> xs =
    new ArrayList<Double>();
xs.add(1.5);
  1. Runs fine
  2. Compile error
  3. Throws ClassCastException
Show the answer

Compile error. The compiler only sees ? extends Number: the list *might* be a List<Integer>, so adding a Double can't be proven safe. **Only null can be added** to a ? extends list.

? super T: safe to write

**List<? super Integer>** could be a List<Integer>, List<Number> or List<Object>. Any of those can hold an Integer, so adding Integers is safe. Reading is the catch: the only type guaranteed for all three is **Object**.

static void fill(List<? super Integer> out) {
    out.add(1);    // ✓ always fits
    out.add(2);
}
🔮 Predict it

Fill it up

What does this print?

static void fill(List<? super Integer> out) {
    out.add(7);
}
void main() {
    List<Object> objs = new ArrayList<>();
    objs.add("hi");
    fill(objs);
    System.out.println(objs);
}
  1. [7]
  2. [hi, 7]
  3. Compile error
Show the answer

A List<Object> is a valid List<? super Integer>, and adding an Integer to it is always safe. The existing "hi" stays: [hi, 7].

⚠️ The trap

Reading from a ? super list

You put Integers in, so you expect Integers out? No: the list might be a List<Object> holding anything. **Reads from ? super Integer give you Object.**

List<? super Integer> out =
    new ArrayList<Number>();
out.add(3);
Integer i = out.get(0); // ✗ compile error
Object o = out.get(0);  // ✓
💼 In the real world

Why API designers care

Wildcards make methods accept more callers without giving up safety. addAll(Collection<? extends E>) is why you can add a List<Integer> to a List<Number>. You'll read them in JDK signatures every day, even if you write them only now and then.

Key takeaways

  1. List<?>: a list of some unknown type — read as Object
  2. ? extends T: safe to read as T, can't add
  3. ? super T: safe to add T, reads give Object
  4. Wildcards appear in variable and parameter types, not in new
🤯 Did you know?

Java's wildcards were designed in the 2004 paper "Adding Wildcards to the Java Programming Language" by Mads Torgersen and colleagues. Torgersen later became the lead designer of C#.

Practice questions

What happens with this code?

List<? extends Number> nums = new ArrayList<Integer>();
nums.add(5);
  1. Runs fine
  2. Compile error
  3. Throws ClassCastException
  4. Throws UnsupportedOperationException
Check your answer

Compile error. The list could really be a List<Double>, so the compiler can't prove adding an Integer is safe. Only null can be added to a ? extends list.

What does this print?

static double total(List<? extends Number> xs) {
    double t = 0;
    for (Number n : xs) { t += n.doubleValue(); }
    return t;
}
void main() {
    List<Integer> ints = List.of(1, 2);
    List<Double> ds = List.of(0.5);
    System.out.println(total(ints) + total(ds));
}
  1. 3.5
  2. Compile error
  3. 3
  4. 12.5
Check your answer

3.5. List<? extends Number> accepts both a List<Integer> and a List<Double>, and every element can be read as a Number: 3.0 + 0.5 = 3.5.

Next: extends or super? One four-letter rule, PECS, settles it every time.