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