Java Wildcards in Generics
Wildcards (?) represent an unknown type in generics. They enable flexible method parameters that accept a wider range of generic types — something type parameters alone can’t always do.
The Problem: Generic Types Are Invariant
List<Integer> ints = List.of(1, 2, 3);
List<Number> nums = ints; // ❌ Compile error!
Even though Integer extends Number, List<Integer> is NOT a subtype of List<Number>. This is invariance — and it exists to prevent:
// If this were allowed:
List<Number> nums = ints;
nums.add(3.14); // adding Double to a List<Integer>! Type safety broken.
Wildcards solve this by expressing “I don’t know the exact type, but I know its bounds.”
Unbounded Wildcard: <?>
“I accept a list of anything”:
public static void printAll(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
printAll(List.of("A", "B")); // ✅
printAll(List.of(1, 2, 3)); // ✅
printAll(List.of(true, false)); // ✅
List<?> vs List<Object>
| Type | Accepts | Can Add |
|---|---|---|
List<?> |
List<String>, List<Integer>, any List |
Only null |
List<Object> |
Only List<Object> |
Any Object |
List<String> strings = List.of("A");
List<?> wildcard = strings; // ✅
List<Object> objects = strings; // ❌ Compile error
Upper Bounded: ? extends T
“I accept T or any subtype of T” — used when you read from the collection:
public static double sum(List<? extends Number> numbers) {
double total = 0;
for (Number n : numbers) {
total += n.doubleValue();
}
return total;
}
sum(List.of(1, 2, 3)); // List<Integer> ✅
sum(List.of(1.5, 2.5)); // List<Double> ✅
sum(List.of(1L, 2L)); // List<Long> ✅
Why You Can’t Add to ? extends
List<? extends Number> list = new ArrayList<Integer>();
// list.add(42); // ❌ Compile error!
// list.add(3.14); // ❌ Compile error!
list.add(null); // ✅ null is the only safe addition
Number n = list.get(0); // ✅ Reading is safe — we know it's at least Number
The compiler doesn’t know the actual type — it could be List<Integer>, List<Double>, or List<BigDecimal>. Adding anything would be unsafe.
Lower Bounded: ? super T
“I accept T or any supertype of T” — used when you write to the collection:
public static void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
List<Integer> ints = new ArrayList<>();
addNumbers(ints); // ✅
List<Number> nums = new ArrayList<>();
addNumbers(nums); // ✅
List<Object> objs = new ArrayList<>();
addNumbers(objs); // ✅
// List<Double> doubles = new ArrayList<>();
// addNumbers(doubles); // ❌ Double is not a supertype of Integer
Reading from ? super
List<? super Integer> list = new ArrayList<Number>();
list.add(42); // ✅ Adding Integer is safe
Object item = list.get(0); // ✅ But reading gives only Object
// Integer i = list.get(0); // ❌ Can't assume it's Integer
PECS — Producer Extends, Consumer Super
The most important rule for wildcards:
| Role | Wildcard | You Can |
|---|---|---|
| Producer (you read from it) | ? extends T |
get() returns T |
| Consumer (you write to it) | ? super T |
add(T) is safe |
| Both read and write | Use exact type T |
Both operations |
PECS in Action: Collections.copy()
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
dest.set(i, src.get(i));
}
}
List<Number> dest = new ArrayList<>(List.of(0, 0, 0));
List<Integer> src = List.of(1, 2, 3);
Collections.copy(dest, src); // ✅ dest consumes, src produces
PECS Decision Guide
Do you GET items from the parameter?
└── Use <? extends T> (producer)
Do you PUT items into the parameter?
└── Use <? super T> (consumer)
Do you do BOTH?
└── Use <T> (exact type)
Wildcard Capture
Sometimes the compiler creates an internal “capture” type for ?:
public static void swap(List<?> list, int i, int j) {
// list.set(i, list.get(j)); // ❌ Compile error — can't put ? back
swapHelper(list, i, j); // ✅ Helper captures the type
}
private static <T> void swapHelper(List<T> list, int i, int j) {
T temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
The helper method captures the wildcard type as a concrete T, enabling both reads and writes.
Practical Examples
Copying Any Subtype
public static <T> List<T> merge(List<? extends T> a, List<? extends T> b) {
List<T> result = new ArrayList<>(a.size() + b.size());
result.addAll(a); // a produces T
result.addAll(b); // b produces T
return result;
}
List<Number> merged = merge(List.of(1, 2), List.of(3.0, 4.0));
// [1, 2, 3.0, 4.0]
Finding Max with Upper Bound
public static <T extends Comparable<? super T>> T max(List<? extends T> list) {
Iterator<? extends T> it = list.iterator();
T max = it.next();
while (it.hasNext()) {
T item = it.next();
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
The signature Comparable<? super T> means T is comparable to itself or any supertype — allowing classes like Integer (which implements Comparable<Integer>) to work even when the actual Comparable is defined on a parent class.
Summary Table
| Wildcard | Accepts | Can Read As | Can Write |
|---|---|---|---|
<?> |
Any type | Object |
Only null |
<? extends Number> |
Number, Integer, Double… |
Number |
Only null |
<? super Integer> |
Integer, Number, Object |
Object |
Integer (and subtypes) |
<Number> (exact) |
Only Number |
Number |
Number |
Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
List<Object> instead of List<?> |
Doesn’t accept List<String> |
Use <?> or <? extends T> |
Adding to ? extends |
Compile error | Use ? super for writing |
Reading specific type from ? super |
Returns Object |
Use ? extends for reading |
| Overusing wildcards | Code becomes unreadable | Use exact types when possible |
Key Takeaways
?= unknown type.<?>accepts any generic type.? extends T= read-only (producer). Use when youget()from the collection.? super T= write-only (consumer). Use when youadd()to the collection.- PECS: Producer Extends, Consumer Super — the golden rule.
List<?>≠List<Object>— the former accepts anyList<T>, the latter onlyList<Object>.- Use wildcard capture helpers when you need both read and write with
?.
What’s Next?
Next: Collections Best Practices — choosing the right collection, decision flowcharts, and production-grade patterns.