Skip to content
Ameen Abozaid
PublishedBEG-031-L05

Wildcards: reading the JDK's signatures

  • Lesson 5 of 6
  • 11 min read
  • Beginner
On This Lesson 11 topics

Lesson Overview

Open the documentation of almost any collection method and you will find a question mark inside the angle brackets: Collection<? extends E>, List<? super T>, Collection<?>. These are wildcards. They look like noise to a beginner, but each carries a precise promise about what the method will do with your collection.

This lesson is a reading lesson. You will not write many wildcards, but you must be able to read them, because every JDK and library signature uses them. The crate picture helps once more: a List<? extends Number> is a crate whose label you cannot read in full, but you know it says "number things". You may take items out and treat them as numbers; you may not put something in, because you do not know which exact kind of number crate it is.

Why this matters: when a method refuses your argument, the signature is the explanation. Reading wildcards turns the error from a mystery into a lookup.

This lesson builds on "Generic classes", "Generic methods", and "Bounded types".

Learning Objectives

By the end of this lesson you will be able to:

  • Explain why List<Integer> is not a List<Number>.
  • Read ?, ? extends T, and ? super T as "unknown type", "unknown subtype of T", and "unknown supertype of T".
  • Write a method that accepts List<Integer> and List<Double> using List<? extends Number>.
  • Explain why you can read from, but not add to, a List<? extends Number>.
  • Use ? super Integer for a list you only write into.
  • Apply the rule "producer extends, consumer super" to read real JDK signatures.

Written Explanation

The gap that wildcards fill

You met this in lesson 2: List<Integer> is not a List<Number>. Suppose we want a method that sums numbers:

java
static double sum(List<Number> nums) { ... }

Then sum(List.of(1, 2, 3)) is rejected, because List.of(1, 2, 3) is a List<Integer>:

text
error: incompatible types: List<Integer> cannot be converted to List<Number>

The reason is safety. If a List<Integer> could be used as a List<Number>, then the method could add a Double to it, and your list of integers would contain a double. The Oracle tutorial puts it this way: generic types are not related merely because their type arguments are.

We could write one sum per type, but that is what generics are meant to avoid. A wildcard states what the method really needs.

? extends T: a producer you read from

java
static double sum(List<? extends Number> nums) {
    double total = 0;
    for (Number n : nums) {
        total += n.doubleValue();
    }
    return total;
}

List<? extends Number> means "a list of some type that is Number or a subtype, and I do not know which". Now List<Integer>, List<Double>, and List<Number> are all accepted:

java
sum(List.of(1, 2, 3));      // 6.0
sum(List.of(1.5, 2.5));     // 4.0

Inside the method, whatever comes out of the list is at least a Number, so reading is safe. Oracle's wording is that an upper bounded wildcard relaxes the restriction so a method can accept a type argument that is a given type or any subtype.

But you cannot put anything in. The list might really be a List<Integer>, or a List<Double>; the compiler cannot know which kind of number is safe to add:

java
List<? extends Number> nums = new ArrayList<Integer>();
nums.add(5);
text
error: incompatible types: int cannot be converted to CAP#1
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Number from capture of ? extends Number

"CAP#1" is the compiler's name for the unknown type. Read the error as "I do not know what is allowed in, so nothing is." The one value you can add is null.

? super T: a consumer you write into

The opposite wildcard has a lower bound:

java
static void fillWithInts(List<? super Integer> target) {
    for (int i = 1; i <= 3; i++) {
        target.add(i);
    }
}

List<? super Integer> means "a list of Integer or of some supertype of Integer". A List<Integer>, a List<Number>, and a List<Object> all qualify, and in each of them adding an Integer is safe. This matches Oracle's lower-bounded wildcard example.

The price is on the reading side. What comes out of such a list is only known to be an Object:

java
List<? super Integer> sink = new ArrayList<Number>();
sink.add(5);                 // fine
Integer x = sink.get(0);     // does not compile
text
error: incompatible types: CAP#1 cannot be converted to Integer

? alone: I do not care about the type

List<?> means a list of unknown type. You can read items as Object and ask for the size, but you cannot add anything except null. It is right for methods that only look at the elements as objects:

java
static void printAll(List<?> items) {
    for (Object o : items) {
        System.out.print(o + " ");
    }
}

The rule of thumb: producer extends, consumer super

Oracle's wildcard guidelines distinguish "in" and "out" variables. The popular short form is PECS: producer extends, consumer super.

The collection...WildcardYou canYou cannot
produces values for you (you read)? extends Tread as Tadd (except null)
consumes values from you (you write)? super Tadd Tread as anything narrower than Object
both read and writeexact List<T>read and addaccept other element types
type does not matter?read as Objectadd (except null)

Reading real JDK signatures

Now the signatures make sense. These come from the Java SE 21 documentation:

SignatureHow to read it
boolean addAll(Collection<? extends E> c) (in List<E>)I add the elements of c into my E list, so c is a producer: it may hold any subtype of E.
static <T> void copy(List<? super T> dest, List<? extends T> src) (Collections)src produces Ts, dest consumes them: its element type may be T or a supertype.
static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) (Collections)The collection produces Ts. T must be comparable, and Comparable<? super T> means a T may be compared by a method defined on a supertype of T.
boolean containsAll(Collection<?> c) (in List)It only asks whether elements are present; the element type of c is irrelevant.

You do not have to write the third form yourself. You only have to recognize its parts: a bound on T, a wildcard on the parameter, and ? super T inside the Comparable. It means the method accepts, for example, a collection of a subclass whose parent class implements Comparable.

Examples

Example 1: sum for any list of numbers (simple)

java
static double sum(List<? extends Number> nums) {
    double total = 0;
    for (Number n : nums) {
        total += n.doubleValue();
    }
    return total;
}

List<Integer> ints = List.of(1, 2, 3);
List<Double> doubles = List.of(1.5, 2.5);
System.out.println(sum(ints));
System.out.println(sum(doubles));
text
6.0
4.0

One method, two list types, no copying.

Example 2: filling a more general list (practical)

java
List<Number> numbers = new ArrayList<>();
List<Object> objects = new ArrayList<>();
fillWithInts(numbers);
fillWithInts(objects);
System.out.println(numbers + " " + objects);
text
[1, 2, 3] [1, 2, 3]

fillWithInts(List<? super Integer>) accepts both. If its parameter were List<Integer>, passing numbers would not compile.

Example 3: collecting from several lists (realistic)

java
List<Number> all = new ArrayList<>();
all.addAll(ints);        // a List<Integer> into a List<Number>
all.addAll(doubles);     // a List<Double> into the same list
System.out.println(all);
text
[1, 2, 3, 1.5, 2.5]

This works because addAll takes Collection<? extends E>. With E = Number, both the integer list and the double list are acceptable producers. Without the wildcard in the signature, mixing sources like this would be impossible.

The same idea in your own helper:

java
static <T> void copyInto(List<? super T> dst, List<? extends T> src) {
    for (T item : src) {
        dst.add(item);
    }
}

copyInto(numbers, List.of(2.5)) appends 2.5 to a List<Number>.

Backend Insight

Service code moves collections between layers: a repository returns a List<Order>, a service passes orders onward, a helper totals amounts from any list of items. Wildcards in method parameters let a helper accept the most flexible input without copying and without losing type safety.

Spring and the JDK use them heavily, so you will read them in method signatures all the time, for example when passing a List<Integer> where a method takes Collection<? extends Number>. In practice, treat your own code like this: write the exact type (List<Order>) for variables and return values, and add wildcards only to method parameters where you accept input flexibly. Prefer the simpler signature when it works; wildcards add reading cost for the next programmer.

Common Mistakes

Mistake 1: Trying to add to List<? extends T>

If you need to add, the parameter should be List<? super T> or an exact List<T>. The compiler error cannot be converted to CAP#1 is the sign.

Mistake 2: Using a wildcard as a return type

A method returning List<? extends Number> forces every caller to deal with the unknown. Return the exact type, such as List<Integer>, and use wildcards on parameters.

Mistake 3: Reaching for List<Object> to accept anything

List<Object> accepts only a list whose element type is exactly Object, not a List<String>. To accept lists of any type, use List<?>.

Mistake 4: Memorizing without the reason

"Extends to read, super to write" is a good reminder, but the reason is what holds: with extends you know everything is at least a T, so reading is safe; with super you know a T fits, so writing is safe. If the rule slips, rebuild it from that.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. In plain words, what does List<? extends Number> accept?

Show solution A1

A1. A list whose element type is Number or any subclass of it, such as List<Integer>, List<Double>, or List<Number>.

A2. True or false: you can call add(5) on a List<? extends Number>.

Show solution A2

A2. False. The compiler does not know which exact type the list holds, so it refuses (int cannot be converted to CAP#1). Only null can be added.

A3. In Collections.copy(List<? super T> dest, List<? extends T> src), which list is read from and which is written to?

Show solution A3

A3. src is read from (a producer, ? extends T) and dest is written to (a consumer, ? super T).

Level B: Application

B1. Write static double largest(List<? extends Number> nums) that returns the largest value as a double. Call it with a List<Integer> and a List<Double>.

Show solution B1

B1.

java
static double largest(List<? extends Number> nums) {
    double best = nums.get(0).doubleValue();
    for (Number n : nums) {
        if (n.doubleValue() > best) best = n.doubleValue();
    }
    return best;
}

largest(List.of(1, 7, 3)) is 7.0 and largest(List.of(2.5, 1.5)) is 2.5.

B2. Write static void addOnes(List<? super Integer> target, int times) that adds 1 the given number of times. Call it with a List<Number> and a List<Object>.

Show solution B2

B2.

java
static void addOnes(List<? super Integer> target, int times) {
    for (int i = 0; i < times; i++) target.add(1);
}

Both List<Number> and List<Object> are accepted; after addOnes(numbers, 2) the list is [1, 1].

B3. Which lines compile?

java
List<Integer> ints = new ArrayList<>();
List<Number> a = ints;              // line 1
List<? extends Number> b = ints;    // line 2
List<Object> c = ints;              // line 3
List<?> d = ints;                   // line 4
Show solution B3

B3. Line 1 and line 3 do not compile (List<Integer> is not a List<Number> or a List<Object>). Lines 2 and 4 compile.

Level C: Challenge

C1. Write static <T> void copyInto(List<? super T> dst, List<? extends T> src) that appends every element of src to dst. Show a call that copies a List<Integer> into a List<Number>.

Show solution C1

C1.

java
static <T> void copyInto(List<? super T> dst, List<? extends T> src) {
    for (T item : src) {
        dst.add(item);
    }
}

List<Number> numbers = new ArrayList<>();
copyInto(numbers, List.of(1, 2, 3));   // from a List<Integer>

C2. A method needs to read numbers from a list and also add the total at the end of the same list. Explain why neither List<? extends Number> nor List<? super Integer> alone fits, and what to use.

Show solution C2

C2. With ? extends Number you can read but not add; with ? super Integer you can add but only read Object. A list that is both read and written needs its exact element type: List<Number> (or a generic <T extends Number> ... List<T> if the element type should vary), because then both operations are known to be safe.

Mini Challenge

Take the JDK signature boolean addAll(Collection<? extends E> c) and explain it in four sentences for a classmate: which collection is the producer, which is the consumer, what is allowed for c, and why new ArrayList<Number>().addAll(List.of(1, 2.5)) works.

Then write your own static double total(Collection<? extends Number> items) and call it with a List<Integer>, a Set<Double>, and a List<Number>.

Check your work: you named the wildcard direction (extends for reading), and none of your three calls needed a cast.

Summary & Next Lesson

Key takeaways

  • List<Integer> is not a List<Number>; wildcards express flexibility safely.
  • ? extends T is for reading (a producer): you can read as T, not add.
  • ? super T is for writing (a consumer): you can add T, but read only Object.
  • ? alone means the element type does not matter.
  • Use PECS: producer extends, consumer super. Put wildcards on parameters, not return types.
  • Signatures such as addAll(Collection<? extends E>) and copy(List<? super T>, List<? extends T>) follow this pattern.

Next lesson

Next, "Demo: building a generic in-memory repository" puts the module together: a generic class with two type parameters, used for two unrelated types, with the compiler guarding every call.

Self-check

  1. Why is List<Integer> not a List<Number>?
  2. Why can you not add to a List<? extends Number>?
  3. What does ? super Integer allow you to do?
  4. What does PECS stand for?
  5. When is List<?> the right parameter type?
Show answers
  1. If it were, you could add a Double through the List<Number> view and corrupt the integer list.
  2. The exact element type is unknown, so no value other than null is known to be safe.
  3. Add Integer values, because the list holds Integer or a supertype. Reading gives only Object.
  4. Producer extends, consumer super.
  5. When the method only treats elements as Object or ignores them, such as printing or checking the size.

References

Practice

Apply this lesson with the module's lab, exercises and quiz.

Lab and exercises Quiz

Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.

Continue to next lesson

Type to search levels, phases, modules and lessons.