Почему в Java нельзя присвоить `List` переменной типа `List`?

«Почему в Java нельзя присвоить `List` переменной типа `List`?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Дженерики в 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 (подстановочные типы) Для безопасной работы с иерархиями используются ограниченные шаблоны:

  1. ? 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).

  2. ? 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.