Wildcards
TL;DR
- In Generics, the question mark
?represents a Wildcard (an unknown type). - It is used in variable assignments and method arguments, not in class/method declarations.
- There are three types of wildcards: Unbounded (
?), Upper Bounded (? extends T), and Lower Bounded (? super T).
Concept
A common misunderstanding in Java is that if Integer is a subtype of Number, then List<Integer> must be a subtype of List<Number>. This is false. Generics are invariant. A List<Integer> is not a List<Number>.
Wildcards solve this problem by allowing flexibility in method parameters and variable assignments.
Types of Wildcards
- Unbounded
<?>: Means “a list of anything”. You can read from it (asObject), but you cannot safely add anything to it. - Upper Bounded
<? extends Number>: Means “a list of Number, or any of its subclasses (Integer, Double)”. You can safely readNumbers from it, but you cannot add to it. - Lower Bounded
<? super Integer>: Means “a list of Integer, or any of its superclasses (Number, Object)”. You can safely writeIntegers into it.
Examples
import java.util.ArrayList;
import java.util.List;
public class WildcardExample {
// Unbounded Wildcard
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.print(elem + " ");
}
System.out.println();
}
// Upper Bounded Wildcard (Read-Only)
public static double sum(List<? extends Number> list) {
double total = 0;
for (Number n : list) {
total += n.doubleValue();
}
// list.add(10); // COMPILE ERROR! You don't know what specific list this is.
return total;
}
// Lower Bounded Wildcard (Write-Only)
public static void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
// Number n = list.get(0); // ERROR! You only know it's at least an Object.
}
public static void main(String[] args) {
List<Integer> ints = new ArrayList<>(List.of(1, 2, 3));
List<Double> doubles = new ArrayList<>(List.of(1.1, 2.2));
printList(ints);
System.out.println("Sum ints: " + sum(ints));
System.out.println("Sum doubles: " + sum(doubles));
List<Number> nums = new ArrayList<>();
addNumbers(nums); // Valid, because Number is a superclass of Integer
}
}
Interview Questions
Q: What is the difference between List<Object> and List<?>?
A: List<Object> is a specific list that holds objects of type Object (or any subclass). You can add anything to it. You cannot pass a List<String> to a method expecting a List<Object>.
List<?> is a wildcard list of an unknown type. You can pass a List<String> or List<Integer> to a method expecting List<?>. However, because the compiler doesn’t know the exact type, you are strictly forbidden from adding anything to a List<?> (except null).
Q: Why can’t you add elements to a List<? extends Number>?
A: If you have a method parameter List<? extends Number>, you could pass a List<Double> to it. If the compiler allowed you to write list.add(new Integer(5)) inside that method, you would be corrupting the original List<Double> by inserting an Integer. To prevent this, the compiler makes ? extends collections strictly read-only.