Raw type

Определение

Raw type — использование обобщённого класса или интерфейса без аргумента типа:

List raw = new ArrayList();          // raw type
List<String> names = new ArrayList<>(); // параметризованный тип

Такая форма сохранена ради совместимости с Java-кодом, написанным до появления generics. В новом коде raw type почти всегда означает потерю проверки типов на этапе компиляции.

Как теряется безопасность

List<String> names = new ArrayList<>();
List raw = names;
raw.add(42);                 // warning, но компилируется
 
String first = names.get(0); // ClassCastException

Объект физически один и тот же. Через raw-ссылку в коллекцию попало значение другого типа — это пример загрязнения кучи (heap pollution). Ошибка проявилась далеко от опасной записи, при сгенерированном компилятором приведении.

Компилятор предупреждает:

unchecked call
unchecked assignment
unchecked cast

Такое предупреждение нельзя глушить автоматически: сначала нужно доказать, что операция действительно безопасна.

Raw type и wildcard — не одно и то же

List raw = new ArrayList<String>();
List<?> unknown = new ArrayList<String>();
  • List отключает значительную часть проверки типов.
  • List<?> означает «список некоторого неизвестного типа» и остаётся типобезопасным.

В List<?> нельзя добавить произвольный объект, кроме null, потому что компилятор не знает фактический тип элемента. Зато читать можно как Object.

int sizeOf(List<?> values) {
    return values.size();
}

Для метода, которому не важен тип элементов, wildcard — правильный выбор.

Где raw type ещё встречается

Из-за стирания типов допустимы class literal и проверка только сырой части типа:

Class<List> listClass = List.class;
 
if (value instanceof List<?> list) {
    System.out.println(list.size());
}

Проверить value instanceof List<String> нельзя: аргумент String недоступен во время выполнения.

На границе со старой библиотекой raw type иногда неизбежен. Тогда опасный участок стоит локализовать, проверить каждый элемент и вернуть новую типизированную коллекцию без общего alias:

List<String> readLegacyNames() {
    List values = legacyApi.load(); // raw type остаётся только на границе
    List<String> result = new ArrayList<>();
    for (Object value : values) {
        result.add(String.class.cast(value));
    }
    return List.copyOf(result);
}

Если без unchecked cast не обойтись, подавление предупреждения должно быть узким и иметь понятное доказательство безопасности.

Ловушки

  • Использовать raw type вместо <?>.
  • Считать warning несущественным, хотя он переносит ошибку из компиляции во время выполнения.
  • Ставить @SuppressWarnings("unchecked") на весь класс.
  • Ожидать, что JVM различает List<String> и List<Integer>.
  • Делать непроверяемое приведение по одному лишь instanceof List<?>.

Вопросы на собеседовании

  1. Что такое raw type и зачем он существует?
  2. Чем List отличается от List<?>?
  3. Что называют heap pollution?
  4. Почему нельзя написать instanceof List<String>?
  5. Когда допустимо подавить unchecked warning?

Связанные заметки

Обобщения generics · Параметризованные классы Generics · List · Преобразование типов Java