Wildcards in Generics

1. What Are Wildcards?

A wildcard ? means:

"I don't know the exact generic type."

Example:

List<?> list;

This can refer to:

List<String>
List<Integer>
List<Double>

2. Upper Bounded Wildcard — ? extends

Use ? extends Type when you want to read values from different subtypes.

List<? extends Number> numbers;

It can accept:

List<Integer>
List<Double>
List<Number>

Example:

static double sum(List<? extends Number> list) {
    double total = 0;

    for (Number n : list) {
        total += n.doubleValue();
    }

    return total;
}

Now:

sum(List.of(1, 2, 3));       // Integer
sum(List.of(1.5, 2.5));      // Double

Remember

? extends Number
       ↓
Number or subclasses
       ↓
Good for READING

3. Unbounded Wildcard — ?

Use ? when you don't care about the exact type.

List<?> list;

It can represent:

List<String>
List<Integer>
List<Double>

Example:

static void printList(List<?> list) {
    for (Object value : list) {
        System.out.println(value);
    }
}

You can call:

printList(List.of("A", "B"));
printList(List.of(1, 2, 3));

You can safely add only null:

list.add(null); // OK
list.add("Hello"); // ERROR

List<?> vs List<Object>

They are not the same.

List<Object> a = new ArrayList<>();
List<?> b = new ArrayList<String>();

List<Object> means the list is specifically a list of Object.

List<?> means the list contains some unknown type.


4. Lower Bounded Wildcard — ? super

Use ? super Type when you want to add/write values.

List<? super Integer> list;

It can represent:

List<Integer>
List<Number>
List<Object>

Example:

static void addNumbers(List<? super Integer> list) {
    list.add(10);
    list.add(20);
}

All of these work:

addNumbers(new ArrayList<Integer>());
addNumbers(new ArrayList<Number>());
addNumbers(new ArrayList<Object>());

Remember

? super Integer
       ↓
Integer or its superclasses
       ↓
Good for WRITING

5. Why List<Integer> Is Not List<Number>

Even though:

Integer extends Number

this does not mean:

List<Integer> extends List<Number>

This is invalid:

List<Number> numbers = new ArrayList<Integer>(); // ERROR

Why?

Because then you could do:

numbers.add(3.14); // Double

and put a Double into a list that is actually List<Integer>.

Wildcards solve this safely:

List<? extends Number> numbers = new ArrayList<Integer>();

6. Wildcards and Reading/Writing

This is the most important rule:

? extends → Read

List<? extends Number> numbers = List.of(1, 2, 3);

Number n = numbers.get(0); // OK

But you cannot add an Integer:

numbers.add(10); // ERROR

Because Java doesn't know the exact type.

It could be:

List<Integer>
List<Double>
List<Float>

? super → Write

List<? super Integer> numbers = new ArrayList<Number>();

numbers.add(10); // OK

Reading gives only Object safely:

Object value = numbers.get(0);

7. Wildcard Capture

Sometimes Java doesn't know the exact type represented by ?.

Example:

void foo(List<?> list) {
    list.set(0, list.get(0)); // ERROR
}

Java knows both values are related to the same unknown type, but ? hides that type.

A helper method can capture the type:

void foo(List<?> list) {
    fooHelper(list);
}

private <T> void fooHelper(List<T> list) {
    list.set(0, list.get(0)); // OK
}

Here Java knows:

T = the actual type of the list

8. Simple Rule to Remember

A useful rule is:

PECS = Producer Extends, Consumer Super

Producer → extends

If something provides/gives you values:

List<? extends Number>

You mainly read from it.

Consumer → super

If something receives values from you:

List<? super Integer>

You mainly write/add to it.


Quick Revision

Wildcard Meaning Main Use
<?> Unknown type When exact type doesn't matter
<? extends T> T or subclass Read
<? super T> T or superclass Write

Easy example

List<? extends Number> source; // READ
List<? super Integer> target;  // WRITE
List<?> anything;              // DON'T CARE

extends → read
super → write
? → unknown type