Вопрос 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]]