У чому різниця між 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. Як працюють дженерики з наслідуванням]]