Ковариантен: принимает 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 по design. Компилятор блокирует записи, потому что
не может 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)]]
[[19. Что такое raw types и почему их следует избегать]]