Wildcards in Java Generics
An Integer is a Number, so surely a List<Integer> is a List<Number>? Surprisingly, no, and Java has a good reason. If it were, you could pass a list of integers to a method expecting numbers, and that method could legally add a Double to it, corrupting the list. Wildcards (?) are how you write methods that accept a family of related types while telling the compiler exactly what you'll do with them: only read, or only write.
Why List<Integer> Isn't a List<Number>
Wild.java:5: error: incompatible types: ArrayList<Integer> cannot be converted to List<Number>
static void inv() { List<Number> n = new ArrayList<Integer>(); }
^If that line were allowed, n.add(2.5) would put a Double into a list that other code believes holds only Integers. So generic types are invariant: List<A> and List<B> are unrelated, even when A extends B.
The Three Wildcards
import java.util.ArrayList;
import java.util.List;
public class Wildcards {
static void printAll(List<? extends Number> numbers) {
double total = 0;
for (Number n : numbers) {
total += n.doubleValue();
}
System.out.println(numbers + " total " + total);
}
static void addMarks(List<? super Integer> target) {
target.add(91);
target.add(88);
}
static int count(List<?> anything) {
return anything.size();
}
public static void main(String[] args) {
printAll(List.of(1, 2, 3));
printAll(List.of(2.5, 0.5));
List<Number> numbers = new ArrayList<>();
addMarks(numbers);
List<Object> objects = new ArrayList<>(List.of("Navika"));
addMarks(objects);
System.out.println(numbers + " " + objects);
System.out.println(count(List.of("a", "b")) + count(List.of(1.5)));
}
}[1, 2, 3] total 6.0
[2.5, 0.5] total 3.0
[91, 88] [Navika, 91, 88]
3How it works
List<? extends Number>means "a list of some type that is aNumberor a subtype". It acceptsList<Integer>andList<Double>. You can read each element as aNumber.List<? super Integer>means "a list of some type that isIntegeror a supertype". It acceptsList<Integer>,List<Number>andList<Object>. You can addIntegers to it.List<?>means "a list of some unknown type". You can call methods likesize()that don't depend on the element type, and read elements only asObject.
Why the Restrictions Exist
The compiler only knows the wildcard's limits, not the actual type, so it blocks anything that might be unsafe:
Wild.java:3: error: incompatible types: int cannot be converted to CAP#1
static void addNumber(List<? extends Number> nums) { nums.add(5); }
^
where CAP#1 is a fresh type-variable:
CAP#1 extends Number from capture of ? extends Numbernums might really be a List<Double>, so adding the Integer 5 could break it. CAP#1 is the compiler's name for "the unknown actual type". The same logic means reading from List<? super Integer> can only give an Object: the list might be a List<Object> holding anything.
Common Mistakes
Declaring List<Number> when callers have List<Integer>. Use List<? extends Number> for read-only input.
Trying to add to a ? extends list. Use ? super if the method writes into the list.
Using List<Object> to mean "any list". List<Object> only accepts List<Object>; List<?> accepts any list.
Interview Corner
Why is List
List<Integer> were a List<Number>, code holding it as List<Number> could add a Double, breaking the guarantee that the list contains only integers. Wildcards express safe relationships instead: List<Integer> is a subtype of List<? extends Number>.
What is the difference between List
, List and List?
`Listis a list of some unknown type; you can read elements only asObjectand can't add (except null).List<? extends T>is a list ofTor a subtype; you can read elements asTbut not add.List<? super T>is a list ofTor a supertype; you can addTvalues but read elements only asObject`.
Practice
- Easy: Write
static double max(List<? extends Number> values)and call it with aList<Integer>and aList<Double>. - Harder: Write
static void fillWithZeros(List<? super Integer> list, int n)and call it with aList<Integer>, aList<Number>and aList<Object>.
Key Takeaways
Was this tutorial helpful?