Питання 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 за дизайном. Компілятор блокує записи, бо не може 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 і чому їх слід уникати]]