Как устроены Java Generics с точки зрения жизненного цикла и что с ними происходит после компиляции и во время выполнения
Что проверяют
Понимаете ли вы стирание типов и можете ли вывести из него ограничения дженериков, а не пересказать их списком. Следом часто спрашивают: раз типы стираются, как Jackson узнаёт, что десериализовать в List<User>?
Как отвечать
Разделите жизнь дженерика на этапы:
- компиляция:
javacпроверяет типы, выводит аргументы для<>и ругается на несовместимые присваивания. Ошибка находится до запуска; - стирание: параметр типа заменяется своей верхней границей.
TстановитсяObject, аT extends Comparable<T>становитсяComparable. В местах чтения компилятор вставляет приведение типа (инструкцияcheckcast), а когда класс реализует обобщённый интерфейс с конкретным типом, напримерComparable<User>, генерирует мостовой метод (bridge method); - выполнение: у
ArrayList<String>иArrayList<Integer>один и тот же класс,getClass()у них совпадает. JVM про аргументы типа у объекта ничего не знает.
Отсюда ограничения: нельзя new T() и new T[], нельзя T.class, нельзя проверить произвольный объект через instanceof List<String>, нельзя перегрузить m(List<String>) и m(List<Integer>), потому что после стирания сигнатуры совпадают. Примитивы как аргументы типа не допускаются, отсюда автобоксинг в List<Integer>.
Важная оговорка: стирается тип объекта, но не объявления. В class-файле остаётся атрибут Signature, и рефлексия через getGenericSuperclass(), Field.getGenericType() и ParameterizedType видит, что поле объявлено как List<User>. На этом держится приём super type token: new TypeReference<List<User>>() {} в Jackson создаёт анонимный подкласс, чей суперкласс хранит полный тип.
Где спотыкаются
Говорят, что после компиляции «вся информация о типах удаляется». Про экземпляры это так, про сигнатуры полей, методов и суперклассов нет.
Не могут объяснить, где вылетит ClassCastException при загрязнении кучи (heap pollution): не там, где чужой объект положили через сырой тип, а там, где его прочитали, в неявном приведении.
Путают List<Object> и List<?>. Первый не является супертипом List<String>, второй является, но добавить в него можно только null.