Коваріантний: приймає List<String>, List<Integer> тощо.
List<?>unknown=List.of("a","b","c");Objecto=unknown.get(0);// ✅// unknown.add("x"); // ❌ не можна писатиList<String>strings=newArrayList<>();process(strings);// ✅ List<?> приймає будь-який параметризований List
🟡 Middle Level
Що відбувається в runtime
Після type erasure обидва типи стають просто List. Різниця тільки в compile-time перевірках.
Компілятор не знає реальний тип елементів. Якщо дозволити записувати Integer,
а список насправді List<String> — буде ClassCastException при читанні.
Raw List vs List<?>
// ❌ Raw type — всі перевірки вимкненіListraw=newArrayList();raw.add("Hello");raw.add(42);// компілятор мовчить!// ✅ Wildcard — перевірки на місціList<?>wildcard=newArrayList<String>();wildcard.add(42);// ❌ compilation error
🔴 Senior Level
Reification та стирання
На рівні байт-коду обидва варіанти — просто List. List<?> безпечніший, бо
компілятор блокує небезпечні операції, а raw type просто вимикає перевірки.
List<?> не дає performance advantages
List<?> не read-only за дизайном. Компілятор блокує записи, бо
не може verify type safety, не тому що трактує колекцію як immutable.
Жодних performance optimizations з цього не випливає.
Capture of Wildcard
// Не можна зробити:voidswap(List<?>list,inti,intj){// var temp = list.get(i);// list.set(i, list.get(j)); // ❌ capture of ?}// Рішення — capture helper:voidswap(List<?>list,inti,intj){swapHelper(list,i,j);}private<T>voidswapHelper(List<T>list,inti,intj){Ttemp=list.get(i);list.set(i,list.get(j));list.set(j,temp);}
Best Practices
// ✅ List<?> для аргументів — максимальна гнучкістьvoidprintAll(List<?>list){}// ✅ List<Object> тільки для гетерогенних контейнерівList<Object>mixed=newArrayList<>();// ❌ Raw List — вимикає всі перевірки// ❌ List<Object> коли потрібен wildcard
🎯 Шпаргалка для співбесіди
Обов’язково знати:
List<Object> — інваріантний, конкретний тип, можна додавати будь-які об’єкти
List<?> — коваріантний, unknown type, читати тільки як Object, не можна записувати
List<String> НЕ є підтипом List<Object> — generics інваріантні
List<?> приймає будь-який параметризований List: List<String>, List<Integer>
List<?> != raw List — wildcard зберігає перевірки, raw вимикає всі
Capture of wildcard — не можна get/set для ? в одному методі, потрібен helper <T>
Часті уточнюючі запитання:
Чи можна додати null в List<?>? — Так, null допустимий для будь-якого типу
**Чим List<?> відрізняється від List
Що таке capture of wildcard? — Компілятор не може співставити тип ? для запису
**Коли використовувати List?** — Гетерогенні контейнери з різними типами
Червоні прапорці (НЕ говорити):
❌ “List<?> — це read-only список” — Компілятор блокує записи через unknown type, не immutable
❌ “List приймає List" — Generics інваріантні, List != List
❌ “List<?> дає performance advantages” — Жодних runtime оптимізацій
❌ “List<?> і raw List — одне й те саме” — Wildcard зберігає перевірки, raw вимикає
Пов’язані теми:
[[11. Що таке дженерики (Generics) в Java]]
[[16. У чому різниця між <? extends T> і <? super T>]]
[[17. Що таке PECS (Producer Extends Consumer Super)]]