Получить доступ

Дженерики и стирание типов

Типобезопасность на компиляции, механизм type erasure и его последствия, дженерик-методы, ограниченные параметры и опасность raw types.

Зачем дженерики

Дженерики дают параметризацию типом: коллекция/класс/метод работает с конкретным типом, а компилятор это проверяет. Ошибка типа ловится до запуска, и не нужны ручные приведения.

До Java 5 коллекции были «сырыми» (List без параметра): клали в них что угодно, доставали Object и приводили вручную — а ошибка вылезала в рантайме как ClassCastException. Дженерики переносят эту проверку на компиляцию.

List<String> list = new ArrayList<>();
list.add("x");
String s = list.get(0); // без приведения — тип проверен компилятором

Что такое type erasure

Дженерики в Java реализованы стиранием (type erasure): компилятор проверяет типы по параметрам, а затем стирает их — заменяет параметр на его границу (или Object, если границы нет) и вставляет нужные приведения. В байткод информация о конкретном T не попадает.

Так сделали ради обратной совместимости при вводе дженериков в Java 5: старый и новый код должны были работать вместе на той же JVM. Цена — дженерики существуют только на этапе компиляции (это «не reified» типы, в отличие, например, от C# или Kotlin inline reified).

// то, что пишем:           // то, во что превращается после стирания:
List<String> l = ...;        // List l = ...;
String s = l.get(0);         // String s = (String) l.get(0); // cast вставил компилятор

Последствия стирания

Раз конкретный тип стёрт, в рантайме его нет: new ArrayList<String>().getClass() == new ArrayList<Integer>().getClass() (один класс List). Отсюда запреты: нельзя T.class, new T(), new T[], нельзя instanceof List<String> (только instanceof List<?>), нельзя иметь static-поле типа T, и нельзя завести два метода, чьи сигнатуры различаются только параметром дженерика (после стирания они совпадут).

Как обойти, когда тип реально нужен в рантайме: передавать его явным токеном Class<T> в метод/конструктор (фабрики так и делают: clazz.getDeclaredConstructor().newInstance()), либо приёмом super type token (анонимный подкласс параметризованного типа, у которого тип-аргумент можно достать рефлексией — так работает чтение generic-типов в Jackson/Spring).

// типобезопасная фабрика через Class-токен — обходит запрет new T()
static <T> T create(Class<T> type) throws Exception {
    return type.getDeclaredConstructor().newInstance();
}

Дженерик-методы и вывод типов

Метод может быть параметризован независимо от класса: <T> T first(List<T> xs). Тип-аргумент обычно не пишут явно — компилятор выводит его из аргументов (type inference). Тот же вывод стоит за diamond (new ArrayList<>()) и за var.

Иногда вывод не справляется (например, из null или вложенных вызовов) — тогда тип задают явно: Collections.<String>emptyList().

Ограниченные параметры (bounds)

Параметр типа можно ограничить: <T extends Comparable<T>> разрешает вызывать на T методы границы (здесь compareTo). Без границы T стирается до Object, и доступны только методы Object; с границей — стирается до неё, и доступны её методы.

Границ может быть несколько: <T extends Number & Comparable<T>> (класс, если есть, идёт первым). Это делает дженерик-код одновременно гибким и типобезопасным.

Raw types — почему нельзя

Raw type — это использование дженерик-класса без параметра (List вместо List<String>). Он существует только ради совместимости с дописьменным кодом и опасен: использование raw type ОТКЛЮЧАЕТ проверки дженериков для всего объекта, возвращая риск ClassCastException в рантайме и unchecked-предупреждения.

Правило: не использовать raw types в новом коде. Если нужен «любой тип» — это List<?> (типобезопасный wildcard), а не сырой List.

Как отвечать на собеседовании

Объясните erasure как механизм (проверка → стирание до границы/Object → вставка cast) и его мотив (совместимость с Java 5), перечислите последствия (один класс в рантайме; нет new T()/T[]/instanceof generic) и обход через Class-токен/super type token. Добавьте bounded types (зачем — методы границы) и почему raw types запрещены (отключают проверки).

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем