Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Ограничения типов и подстановочные знаки

Параметр 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 или конкретный тип.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.