Bounded types and wildcards
A parameter T without bounds can be anything, so only the methods of Object are available on it. If a method needs, say, to add numbers or compare values, the type is bounded.
The extends bound
The notation <T extends Number> means "T is Number or one of its subclasses": Integer, Double, Long. Inside the method T now has the methods of the Number class, such as doubleValue():
<T extends Number> double sum(List<T> numbers) {
double total = 0;
for (T n : numbers) {
total += n.doubleValue();
}
return total;
}
void main() {
IO.println(sum(List.of(1, 2, 3)));
IO.println(sum(List.of(1.5, 2.5)));
}Output:
6.0
4.0You cannot pass a list of strings to sum() — the compiler will not let it through.
Bounding by an interface
An interface can follow extends too. <T extends Comparable<T>> means "the elements can be compared with each other" — that is enough to find the largest one:
<T extends Comparable<T>> T max(List<T> items) {
T best = items.get(0);
for (T item : items) {
if (item.compareTo(best) > 0) {
best = item;
}
}
return best;
}
void main() {
IO.println(max(List.of(3, 9, 4)));
IO.println(max(List.of("pear", "apple", "quince")));
}Output:
9
quinceSeveral bounds are joined with &: <T extends Number & Comparable<T>>.
Lists are not inherited
Integer is a subclass of Number, but List<Integer> is not a subtype of List<Number>. Otherwise you could put a decimal into a list of integers through a variable of type List<Number>. That is why this method will not accept a list of Integer:
double total(List<Number> numbers) {
return numbers.size();
}
void main() {
List<Integer> ints = List.of(1, 2);
IO.println(total(ints));
}Output:
Main.java:7: error: method total in class Main cannot be applied to given types;
IO.println(total(ints));
^
required: List<Number>
found: List<Integer>
reason: argument mismatch; List<Integer> cannot be converted to List<Number>The ? extends wildcard
The solution is the wildcard ?. The type List<? extends Number> means "a list of something that is a Number". You can read numbers from such a list, but you cannot add to it:
double total(List<? extends Number> numbers) {
double sum = 0;
for (Number n : numbers) {
sum += n.doubleValue();
}
return sum;
}
void main() {
List<Integer> ints = List.of(1, 2);
List<Double> doubles = List.of(0.5, 0.25);
IO.println(total(ints));
IO.println(total(doubles));
}Output:
3.0
0.75The ? super wildcard
List<? super Integer> is "a list you can put an Integer into": List<Integer>, List<Number> and List<Object> all fit. Such a parameter is needed by a method that writes values:
void fillWithOnes(List<? super Integer> target, int count) {
for (int i = 0; i < count; i++) {
target.add(1);
}
}
void main() {
List<Number> numbers = new ArrayList<>(List.of(2.5));
fillWithOnes(numbers, 2);
IO.println(numbers);
}Output:
[2.5, 1, 1]A simple rule: if a method only reads from a collection, use ? extends; if it only writes, use ? super. This rule (PECS) and type erasure are covered in detail in the PECS and type erasure lesson.
Common mistake
You cannot add to a ? extends list: the compiler does not know which list it is exactly — of integers or of decimals:
void addZero(List<? extends Number> numbers) {
numbers.add(0);
}
void main() {
}Output:
Main.java:2: error: no suitable method found for add(int)
numbers.add(0);
^
method List.add(CAP#1) is not applicable
(argument mismatch; int cannot be converted to CAP#1)CAP#1 is the compiler's name for the unknown type hidden behind ?. If the method must add elements, use ? super Integer or a specific type.