Вопрос 16 · Раздел 20

В чём разница между и ?

Ограничение сверху: тип T или любой его наследник.

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

<? extends T> — ковариантность (Producer).

  • Можно читать элементы как тип T.
  • Нельзя записывать (кроме null).
List<? extends Number> numbers = List.of(1, 2, 3);
Number n = numbers.get(0);  // ✅ можно читать
// numbers.add(4);  // ❌ нельзя писать

<? super T> — контрвариантность (Consumer).

  • Можно записывать элементы типа T.
  • Читать можно только как Object.
List<? super Integer> integers = new ArrayList<>();
integers.add(42);  // ✅ можно писать
// Integer i = integers.get(0);  // ❌ можно читать только как Object

Простое правило (PECS):

  • Producer Extends — читаем данные → ? extends T
  • Consumer Super — записываем данные → ? super T

🟡 Middle Level

Ковариантность — <? extends T>

Ограничение сверху: тип T или любой его наследник.

List<? extends Number> nums = new ArrayList<Integer>();
Number n = nums.get(0);  // ✅ Integer — это Number
// nums.add(42);  // ❌ компилятор не знает, Integer это или Double

Почему нельзя писать: List<? extends Number> может быть List<Integer> или List<Double>. Если разрешить запись Double, а там Integer — нарушение целостности.

Контрвариантность — <? super T>

Ограничение снизу: тип T или любой его родитель.

List<? super Integer> target = new ArrayList<Number>();
target.add(42);  // ✅ Integer поместится в Number или Object
Object o = target.get(0);  // ✅ читать можно только как Object

Почему нельзя читать типизированно: List<? super Integer> может быть List<Number> или List<Object>. Не гарантируется, что там лежат именно Integer.

Сравнение

Характеристика <? extends T> <? super T>
Роль Producer (источник) Consumer (приёмник)
Чтение ✅ Как T ❌ Только как Object
Запись ❌ Запрещена ✅ Как T
Иерархия T и все, кто ниже T и все, кто выше

Capture of Wildcard

Частая ошибка:

void reverse(List<?> list) {
    // list.set(0, list.get(0)); // ОШИБКА: capture of ?
}

Компилятор не может сопоставить тип, который «вытащил», с типом для записи. Решение: вспомогательный generic-метод, который «захватывает» тип:

void reverse(List<?> list) { reverseHelper(list); }
private <T> void reverseHelper(List<T> list) {
    // теперь T известен
}

🔴 Senior Level

Вариативность на месте использования (Use-site variance)

Java использует вариативность в месте использования — в параметрах метода. Это отличается от Kotlin/Scala, где есть вариативность на месте объявления (out T / in T).

Wildcards НЕ предотвращают аллокации

Использование wildcards в сигнатурах методов не предотвращает аллокации. ArrayList<Integer> аллоцирует Integer objects независимо от того, передан ли он как List<? extends Number>. Wildcards — это только compile-time type checking.

Unbounded Wildcard <?>

Это фактически <? extends Object>. Полезно, когда код использует только методы Object:

void printSize(List<?> list) {
    System.out.println(list.size());  // только методы Object
}

Best Practices

// ✅ extends — для извлечения данных (Producer)
public double sum(List<? extends Number> numbers) { ... }

// ✅ super — для накопления данных (Consumer)
public void fill(List<? super Integer> list, int value) { ... }

// ✅ Без wildcard когда нужно и read, и write
public <T> void swap(List<T> list, int i, int j) { ... }

// ❌ Extends для записи
// ❌ Super для чтения конкретного типа

🎯 Шпаргалка для интервью

Обязательно знать:

  • <? extends T> — ковариантность (Producer): можно читать как T, нельзя записывать (кроме null)
  • <? super T> — контрвариантность (Consumer): можно записывать T, читать только как Object
  • List<String> НЕ наследует List<Object> — дженерики инвариантны
  • Правило PECS: Producer Extends, Consumer Super
  • Wildcards — use-site variance (в отличие от Kotlin’s declaration-site variance)
  • <?> = <? extends Object> — для случаев когда нужны только методы Object

Частые уточняющие вопросы:

  • Почему <? extends T> запрещает запись? — Компилятор не знает реальный тип (может быть List), запись нарушит целостность
  • Что можно записать в List<? super Integer>? — Integer и его наследников, т.к. они поместятся в любой супертип
  • Что такое capture of wildcard? — Компилятор не может связать get() с set() для ?, нужен generic helper метод
  • Когда wildcard НЕ нужен? — Когда нужно и читать, и писать — использовать <T>

Красные флаги (НЕ говорить):

  • ❌ “List<? extends Number> может быть List" — Да, но нельзя добавить Integer в него
  • ❌ “<? super T позволяет читать как T” — Можно читать только как Object
  • ❌ “Wildcards предотвращают аллокации” — Это только compile-time проверка
  • ❌ “List<?> — это read-only список” — Компилятор блокирует записи из-за неизвестного типа, не immutable

Связанные темы:

  • [[11. Что такое дженерики (Generics) в Java]]
  • [[13. Что такое type erasure (стирание типов)]]
  • [[17. Что такое PECS (Producer Extends Consumer Super)]]
  • [[21. В чём разница между List<?> и List]]
  • [[24. Как работают дженерики с наследованием]]