Параметризованные методы и конструкторы

Generic-метод объявляет собственные параметры типа перед возвращаемым типом. Они не обязаны совпадать с параметрами класса.

public static <T> T first(List<? extends T> values) {
    if (values.isEmpty()) {
        throw new NoSuchElementException();
    }
    return values.get(0);
}
 
String firstName = first(List.of("Ada", "Linus"));

Компилятор обычно выводит T из аргументов и целевого типа. Его можно указать явно: Util.<String>first(names).

Bounds

public static <T extends Number & Comparable<? super T>>
T max(List<? extends T> values) {
    return values.stream()
            .max(Comparator.naturalOrder())
            .orElseThrow();
}

Сначала указывается не более одного class bound, затем интерфейсы. Ограничение определяет операции, доступные внутри метода.

Generic constructor

Конструктор может иметь собственный type parameter даже у не-generic класса:

final class Box {
    private final String text;
 
    <T> Box(T value, Function<? super T, String> formatter) {
        this.text = formatter.apply(value);
    }
}
 
Box box = new Box(42, Object::toString);

Запись <T> стоит перед именем конструктора. Это параметр одного вызова, он не становится параметром типа Box.

Ограничения erasure

  • нельзя new T(), T.class, new T[10];
  • нельзя перегрузить методы, чьи параметры после erasure совпадают;
  • static-метод не видит type parameter класса, но может объявить собственный;
  • primitive нельзя использовать как type argument напрямую.

На собеседовании

Где пишется <T> generic-метода? Перед возвращаемым типом.

Чем <T> отличается от <?>? T связывает несколько мест одним известным компилятору типом; wildcard описывает неизвестный тип.

Может ли обычный класс иметь generic-конструктор? Да.

Почему нельзя new T()? После erasure нет runtime-информации о конкретном конструкторе T.

Связано: Обобщения generics, Обобщения и коллекции.