Дженерики инвариантны
List<String> НЕ является подтипом List<Object>, хотя String — подтип Object. Это инвариантность, и она намеренная: будь List<String> подтипом List<Object>, через ссылку List<Object> можно было бы положить в список Integer и сломать его типобезопасность.
Контраст с массивами: массивы ковариантны (String[] — подтип Object[]), и именно поэтому небезопасны — запись неподходящего типа компилируется, но падает в рантайме как ArrayStoreException. Дженерики ловят эту ошибку на компиляции, что лучше.
Object[] arr = new String[1];
arr[0] = 42; // компилируется, но в рантайме ArrayStoreException
// List<Object> l = new ArrayList<String>(); // а это даже не скомпилируется
Безграничный wildcard ?
List<?> — это «список чего-то конкретного, но неизвестно чего». Он полезен, когда тип элементов не важен (например, метод, печатающий размер любого списка).
Из него можно читать как Object и нельзя ничего класть (кроме null): компилятор не знает фактический тип, поэтому запрещает запись, чтобы не нарушить типобезопасность.
? extends T — ковариантность (producer)
List<? extends Number> — список Number ИЛИ любого его подтипа (Integer, Double…). Из такого списка безопасно ЧИТАТЬ как Number, но нельзя в него ПИСАТЬ (кроме null): фактический тип может быть List<Integer>, и положить туда Double было бы ошибкой.
Это для источников данных (producer): метод, который только берёт элементы из коллекции.
double sum(List<? extends Number> xs) { // принимает List<Integer>, List<Double>...
double s = 0;
for (Number n : xs) s += n.doubleValue(); // читаем как Number — ок
return s; // xs.add(...) — запрещено компилятором
}
? super T — контравариантность (consumer)
List<? super Integer> — список Integer ИЛИ любого его супертипа (Number, Object). В такой список безопасно ПИСАТЬ Integer (он подойдёт любому супертипу), но при чтении компилятор может гарантировать лишь Object.
Это для приёмников данных (consumer): метод, который только кладёт элементы в коллекцию.
void fillWithZeros(List<? super Integer> dst, int n) { // List<Integer>, List<Number>, List<Object>
for (int i = 0; i < n; i++) dst.add(0); // писать Integer — ок
}
PECS
Правило выбора границы — PECS: Producer Extends, Consumer Super. Если параметр служит ИСТОЧНИКОМ элементов (из него читают) — ? extends T; если ПРИЁМНИКОМ (в него пишут) — ? super T; если и то и другое — точный тип T без wildcard.
Классика из JDK: Collections.copy(List<? super T> dest, List<? extends T> src) — приёмник super, источник extends. Так же устроены сигнатуры Stream.forEach(Consumer<? super T>), Comparator и многие другие — это делает API максимально гибким для вызывающих.

Как отвечать на собеседовании
Объясните инвариантность дженериков через пример небезопасной записи и противопоставьте ковариантным массивам (ArrayStoreException в рантайме). Разложите ? extends (читать, producer) и ? super (писать, consumer) и сформулируйте PECS, подкрепив сигнатурой Collections.copy.