Ограничения типов и подстановочные знаки
Параметр T без ограничений может быть чем угодно, поэтому у него доступны только методы Object. Если методу нужно, например, складывать числа или сравнивать значения, тип ограничивают.
Ограничение extends
Запись <T extends Number> значит «T — это Number или его подкласс»: Integer, Double, Long. Внутри метода у T теперь есть методы класса Number, например 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)));
}Результат:
6.0
4.0Список строк передать в sum() не получится — компилятор не пропустит.
Ограничение интерфейсом
После extends можно указать и интерфейс. <T extends Comparable<T>> означает «элементы умеют сравниваться друг с другом» — этого достаточно, чтобы найти наибольший:
<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("груша", "яблоко", "айва")));
}Результат:
9
яблокоНесколько ограничений перечисляют через &: <T extends Number & Comparable<T>>.
Списки не наследуются
Integer — подкласс Number, но List<Integer> — не подтип List<Number>. Иначе в список целых чисел можно было бы положить дробное через переменную типа List<Number>. Поэтому такой метод не примет список Integer:
double total(List<Number> numbers) {
return numbers.size();
}
void main() {
List<Integer> ints = List.of(1, 2);
IO.println(total(ints));
}Результат:
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>Подстановочный знак ? extends
Решение — подстановочный знак ?. Тип List<? extends Number> означает «список чего-то, что является Number». Из такого списка можно читать числа, а добавлять в него нельзя:
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));
}Результат:
3.0
0.75Подстановочный знак ? super
List<? super Integer> — «список, в который можно положить Integer»: подойдут List<Integer>, List<Number> и List<Object>. Такой параметр нужен методу, который записывает значения:
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);
}Результат:
[2.5, 1, 1]Простое правило: если метод только читает из коллекции — ? extends, если только пишет — ? super. Подробнее это правило (PECS) и стирание типов разобраны в уроке PECS и стирание типов.
Частая ошибка
В список ? extends нельзя добавлять: компилятор не знает, какой именно это список — целых чисел или дробных:
void addZero(List<? extends Number> numbers) {
numbers.add(0);
}
void main() {
}Результат:
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 — так компилятор называет неизвестный тип, скрытый за ?. Если метод должен добавлять элементы, используйте ? super Integer или конкретный тип.