Питання 19 · Розділ 20

Що таке raw types і чому їх слід уникати

Raw type — це дженерик-клас без type arguments:

Мовні версії: English Russian Ukrainian

🟢 Junior Level

Raw type — це використання дженерик-класу без вказання типу.

// ✅ Параметризований тип
List<String> list = new ArrayList<>();
list.add("Hello");
String s = list.get(0);  // без cast

// ❌ Raw type — без типу
List list = new ArrayList();  // raw type!
list.add("Hello");
list.add(42);  // компілятор не лається, але це баг!
String s = (String) list.get(0);  // потрібен cast
String s2 = (String) list.get(1); // ClassCastException!

Чому варто уникати:

  1. ❌ Немає перевірки типів на компіляції
  2. ❌ Потрібен cast
  3. ❌ Ризик ClassCastException в runtime

🟡 Middle Level

Що таке raw type

Raw type — це дженерик-клас без type arguments:

// Дженерик-клас
public class Box<T> {
    private T value;
    public void set(T value) { this.value = value; }
    public T get() { return value; }
}

// Параметризоване використання
Box<String> stringBox = new Box<>();  // ✅

// Raw type
Box rawBox = new Box();  // ❌ warning: raw type

Що відбувається:

Box rawBox = new Box();
rawBox.set("Hello");
rawBox.set(42);  // компілятор не лається!

// Після type erasure:
Box rawBox = new Box();
rawBox.set("Hello");
rawBox.set(Integer.valueOf(42));  // все зберігається

// При читанні — ClassCastException
String s = (String) rawBox.get();  // OK
Integer i = (Integer) rawBox.get();  // OK
String s2 = (String) rawBox.get();  // ClassCastException!

Типові помилки

  1. Legacy code сумісність: ```java // Старий код до Java 5 List list = new ArrayList(); list.add(“Hello”);

// Новий код List strings = list; // warning: unchecked conversion


2. **Змішування raw та parameterized:**
```java
Map<String, List<String>> map = new HashMap<>();
Map rawMap = map;  // raw type — втрачається безпека
rawMap.put("key", 42);  // компілятор не лається

// Потім ClassCastException
List<String> list = map.get("key");  // ClassCastException!

Практичне застосування

Єдиний випадок raw type — getClass():

// getClass() повертає Class<? extends |X|>, не raw type.
// Присвоювання в Class — unchecked conversion, не intended design.
Class<?> clazz = "Hello".getClass();
Class rawClass = "Hello".getClass();  // теж OK, але unchecked conversion

// Але краще використовувати wildcard
Class<?> better = "Hello".getClass();

Коли raw types допустимі

  • Сумісність з pre-Java 5 бібліотеками
  • Певні reflection сценарії
  • Class literals: Class.class (хоча краще Class<?>)

🔴 Senior Level

Internal Implementation

Type erasure з raw types:

// Raw type — всі type checks вимкнені
List<String> typed = new ArrayList<>();
List raw = typed;

raw.add(42);  // компілятор пропускає!
raw.add(true); // компілятор пропускає!

// Але type erasure зберігає дані як Object
// raw = typed після erasure — один і той самий об'єкт

Unchecked warnings:

// Компілятор видає warnings:
// warning: [unchecked] unchecked call to add(E) as a member of raw type List
raw.add(42);

// warning: [unchecked] unchecked conversion
List<String> list = raw;

// warning: [rawtypes] raw type
Box box = new Box();

Архітектурні Trade-offs

Raw types vs параметризовані:

Raw types Parameterized
Немає перевірки типів Compile-time перевірка
Ризик ClassCastException Type-safe
Legacy сумісність Сучасний код
Без cast при access Cast не потрібен

Edge Cases

1. Raw type в наслідуванні:

public class Node<T> {
    private T data;
    public T getData() { return data; }
}

public class RawNode extends Node {  // raw type!
    @Override
    public Object getData() { return super.getData(); }
}

// Після erasure — bridge methods працюють коректно
// Але втрачається type safety

2. Raw type і generic методи:

public class Util {
    public static <T> T identity(T t) { return t; }
}

// Виклик через raw type
Util u = new Util();  // raw type
Object result = u.identity("Hello");  // T виводиться як Object

// Правильно
Object result2 = Util.identity("Hello");  // T = String

3. Raw type в масивах:

// Масиви generic класів — raw type
Box<String>[] boxes = new Box[10];  // warning: raw type

// Краще
List<Box<String>> boxes = new ArrayList<>();

Продуктивність

Raw types vs parameterized:
- Runtime: однаково (type erasure)
- Compile time: raw types швидше (немає перевірки)
- Безпека: parameterized значно безпечніше

Продуктивність однакова, але raw types risky

Production Experience

Legacy migration:

// До Java 5
public class OldDao {
    public List findAll() {
        return jdbcTemplate.query("SELECT * FROM users", ...);
    }
}

// Після міграції
public class NewDao {
    public List<User> findAll() {
        return jdbcTemplate.query("SELECT * FROM users", ...);
    }
}

// Transitional period — raw types warnings
List users = oldDao.findAll();  // raw type
List<User> typedUsers = newDao.findAll();  // parameterized

@SuppressWarnings(“rawtypes”):

// Тільки коли unavoidable
@SuppressWarnings("rawtypes")
public void legacyInterop() {
    List rawList = legacyMethod();
    // ...
}

// ❌ Не приглушуйте warnings без причини
// ✅ Фіксите root cause

Best Practices

// ✅ Завжди використовуйте type arguments
List<String> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();

// ✅ Diamond operator (Java 7+)
List<String> list = new ArrayList<>();  // не new ArrayList<String>()

// ✅ Wildcards коли тип невідомий
public void process(List<?> list) { }

// ⚠️ Raw types тільки для legacy коду
// ⚠️ Приглушуйте warnings тільки коли unavoidable

// ❌ Raw types в новому коді
// ❌ Змішування raw та parameterized
// ❌ Ігнорування unchecked warnings

🎯 Шпаргалка для співбесіди

Обов’язково знати:

  • Raw type — використання дженерик-класу без type argument: List list = new ArrayList()
  • Raw types вимикають compile-time перевірку типів — ризик ClassCastException в runtime
  • Змішування raw та parameterized типів заражає весь ланцюжок unchecked warnings
  • Єдиний допустимий raw type — legacy код та певні reflection сценарії
  • Після type erasure raw та parameterized — один і той самий bytecode
  • @SuppressWarnings("rawtypes") — тільки коли unavoidable, не для приглушення без причини

Часті уточнюючі запитання:

  • Коли raw types допустимі? — Legacy pre-Java 5 бібліотеки, деякі reflection сценарії
  • Що буде при змішуванні raw та parameterized? — Unchecked warnings, втрата type safety
  • Raw type vs wildcard <?>? — Raw вимикає всі перевірки, wildcard зберігає перевірки
  • Чи можна створити масив generic класів? — Так, але буде raw type: new Box[10]

Червоні прапорці (НЕ говорити):

  • ❌ “Raw types швидші за parameterized” — Runtime продуктивність однакова
  • ❌ “Raw types — нормальна практика в новому коді” — Завжди використовуйте type arguments
  • ❌ “Raw type — це те саме що <?>” — Raw вимикає перевірки, wildcard зберігає
  • ❌ “SuppressWarnings вирішує проблему” — Warning залишається, потрібно фіксити root cause

Пов’язані теми:

  • [[11. Що таке дженерики (Generics) в Java]]
  • [[12. У чому переваги використання дженериків]]
  • [[13. Що таке type erasure (стирання типів)]]
  • [[21. У чому різниця між List<?> і List]]