Ответ
Дженерики в Java инвариантны. Это означает, что List<Integer> не является подтипом List<Number>, даже если Integer наследуется от Number. Это фундаментальное ограничение системы типов, предотвращающее ошибки во время выполнения.
Проблема, которую предотвращает инвариантность:
List<Integer> intList = new ArrayList<>();
intList.add(42);
// Предположим, компилятор разрешил это (но он не разрешает):
List<Number> numberList = intList; // Ошибка компиляции: несовместимые типы
// Тогда стало бы возможным добавить Double в список Integer:
numberList.add(3.14); // Катастрофа! В intList теперь Double!
// Это привело бы к ClassCastException при извлечении:
Integer i = intList.get(1); // Ошибка: Double нельзя привести к Integer
Решение: Wildcards (подстановочные типы) Для безопасной работы с иерархиями используются ограниченные шаблоны:
-
? extends T(Producer/«чтение»):List<Integer> ints = Arrays.asList(1, 2, 3); List<? extends Number> numbers = ints; // OK Number n = numbers.get(0); // Чтение разрешено // numbers.add(10); // Ошибка компиляции! Нельзя добавлять.Коллекция становится read-only (кроме
null). -
? super T(Consumer/«запись»):List<Number> nums = new ArrayList<>(); List<? super Integer> intConsumer = nums; // OK intConsumer.add(100); // Добавление Integer разрешено // Integer i = intConsumer.get(0); // Ошибка! Можно читать только как Object. Object obj = intConsumer.get(0);
PECS (Producer Extends, Consumer Super): Мнемоника для выбора wildcard:
- Если структура производит (
produces) элементы типаT(например, возвращает из метода), используйте? extends T. - Если структура потребляет (
consumes) элементы типаT(например, принимает как параметр), используйте? super T.