Вопрос 21 · Раздел 20

В чём разница между List и List?

После type erasure оба типа становятся просто List. Разница только в compile-time проверках.

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

List<Object> — конкретный тип: список объектов.

  • Можно добавлять любые объекты (String, Integer и т.д.).
  • Инвариантен: List<Object> НЕ является родителем List<String>.
List<Object> objects = new ArrayList<>();
objects.add("Hello");
objects.add(42);  // ✅ можно

// Но:
List<String> strings = new ArrayList<>();
// process(objects);  // ✅
// process(strings);  // ❌ List<String> ≠ List<Object>

List<?> — список неизвестного типа.

  • Можно читать элементы только как Object.
  • Нельзя записывать (кроме null).
  • Ковариантен: принимает List<String>, List<Integer>, и т.д.
List<?> unknown = List.of("a", "b", "c");
Object o = unknown.get(0);  // ✅
// unknown.add("x");  // ❌ нельзя писать

List<String> strings = new ArrayList<>();
process(strings);  // ✅ List<?> принимает любой параметризованный List

🟡 Middle Level

Что происходит в runtime

После type erasure оба типа становятся просто List. Разница только в compile-time проверках.

// List<Object> — инвариантен
void process1(List<Object> list) { }
process1(new ArrayList<String>());  // ❌ compilation error

// List<?> — ковариантен
void process2(List<?> list) { }
process2(new ArrayList<String>());  // ✅ OK

Почему List<?> запрещает запись

List<?> unknown = new ArrayList<String>();
// unknown.add(42);  // ❌

Компилятор не знает реальный тип элементов. Если разрешить записывать Integer, а список на самом деле List<String> — будет ClassCastException при чтении.

Raw List vs List<?>

// ❌ Raw type — все проверки отключены
List raw = new ArrayList();
raw.add("Hello");
raw.add(42);  // компилятор молчит!

// ✅ Wildcard — проверки на месте
List<?> wildcard = new ArrayList<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

// Нельзя сделать:
void swap(List<?> list, int i, int j) {
    // var temp = list.get(i);
    // list.set(i, list.get(j));  // ❌ capture of ?
}

// Решение — capture helper:
void swap(List<?> list, int i, int j) {
    swapHelper(list, i, j);
}
private <T> void swapHelper(List<T> list, int i, int j) {
    T temp = list.get(i);
    list.set(i, list.get(j));
    list.set(j, temp);
}

Best Practices

// ✅ List<?> для аргументов — максимальная гибкость
void printAll(List<?> list) { }

// ✅ List<Object> только для гетерогенных контейнеров
List<Object> mixed = new ArrayList<>();

// ❌ 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?** — 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 и почему их следует избегать]]