В чём разница между extends T> и super T>?
Ограничение сверху: тип T или любой его наследник.
🟢 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, читать только как ObjectList<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. Как работают дженерики с наследованием]]