Обобщения generics
Generics параметризуют класс, интерфейс или метод типом. Они переносят проверку совместимости в компиляцию и уменьшают число явных приведений.
final class Box<T> {
private final T value;
Box(T value) {
this.value = value;
}
T get() {
return value;
}
}T — параметр типа в объявлении, String в Box<String> — аргумент типа.
Инвариантность
List<Integer> не является подтипом List<Number>, хотя Integer —
подтип Number. Иначе в список целых можно было бы добавить Double.
List<Integer> integers = List.of(1, 2);
List<? extends Number> numbers = integers;
Number first = numbers.get(0);
// numbers.add(3); // запрещено: точный тип списка неизвестенWildcards и PECS
? extends T— producer: безопасно читать какT, нельзя добавлять;? super T— consumer: можно добавлятьT, чтение даётObject;?— неизвестный тип, когда конкретный параметр не важен.
static <T> void copy(
List<? extends T> source,
List<? super T> target) {
target.addAll(source);
}PECS — удобная подсказка для входных параметров API, но не замена пониманию, кто читает и кто записывает.
Bounds
static <T extends Comparable<? super T>> T max(List<? extends T> values) {
return values.stream().max(Comparator.naturalOrder()).orElseThrow();
}У параметра может быть верхняя граница: сначала класс, затем интерфейсы
через &. Нижняя граница доступна wildcard (? super T), но не объявлению
параметра типа.
Type erasure
Большинство информации о параметрах стирается после компиляции. В байт-коде остаются границы, casts и при необходимости bridge methods. Поэтому нельзя:
- создать
new T(); - написать
T.class; - проверить
value instanceof List<String>; - создать
new T[10]; - использовать примитив как
List<int>; - иметь static-поле типа параметра класса.
Можно проверить instanceof List<?>. Отдельные сведения о generics в
сигнатурах сохраняются для reflection, но объект обычно не знает, что он
List<String>.
Raw type (List без аргумента) отключает часть проверок и может привести к
heap pollution: переменная параметризованного типа фактически содержит
объект другого типа. Предупреждение unchecked нельзя подавлять без анализа.
Связанные темы: Коллекции, Интерфейсы Java, Iterable.
Вопросы для собеседования
- Почему
List<Integer>неList<Number>? Generics инвариантны. - Что означает PECS? Producer extends, consumer super.
- Что возвращает чтение из
List<? super Integer>? ТолькоObjectбез дополнительного знания. - Почему нельзя
new T()? После erasure JVM не знает конкретный T. - Что такое raw type? Использование generic-типа без аргумента, теряющее проверки.
- Можно ли перегрузить методы
m(List<String>)иm(List<Integer>)? Нет, после стирания сигнатуры совпадут. - Зачем bridge method? Сохранить полиморфизм после erasure.