Как появились Java Generics и какую проблему они закрыли
Что проверяют
Знаете ли вы не только синтаксис List<String>, но и почему generics в Java устроены через стирание типов и какие ограничения из этого следуют. Следом почти наверняка спросят про type erasure, wildcard-типы и правило PECS.
Как отвечать
Generics появились в Java 5 (2004 год) по JSR 14. Основой стал проект Generic Java (GJ), среди авторов которого был Мартин Одерски, будущий создатель Scala.
Проблема до этого: коллекции хранили Object. В List можно было положить что угодно, при чтении приходилось писать (String) list.get(0), а ошибка типа всплывала только во время выполнения как ClassCastException. Generics перенесли проверку на этап компиляции и убрали ручные приведения.
Главное решение в дизайне это стирание типов (type erasure). Параметры типа существуют только для компилятора: в байткоде List<String> и List<Integer> это один и тот же List, а приведения расставляет компилятор. Сделано ради совместимости: старый код и скомпилированные библиотеки продолжили работать с новыми коллекциями, а JVM почти не менялась.
Цена стирания:
- нельзя
new T()иnew T[n], нуженClass<T>или фабрика; - нельзя
instanceof List<String>, проверяется только сырой тип; - примитивы не подставляются:
List<int>не скомпилируется, отсюда упаковка вInteger; - перегрузки с параметрами
List<String>иList<Integer>конфликтуют, после стирания у них одинаковая сигнатура.
Сравнение в плюс: массивы в Java ковариантны и проверяются в рантайме (ArrayStoreException), generics инвариантны и проверяются при компиляции. В C# generics сохраняются в рантайме, и List<int> там работает без упаковки.
Где спотыкаются
Думают, что List<Object> это супертип List<String>. Нет: generics инвариантны, для гибкости нужен List<? extends Object> или List<?>.
Говорят, что параметр типа всегда доступен через рефлексию. У объекта в рантайме он стёрт и сохраняется только в сигнатурах полей, методов и суперклассов. На этом держатся приёмы вроде TypeReference в Jackson.
Используют сырые типы (List без параметра) в новом коде. Компилятор выдаёт предупреждение, а проверка типов, ради которой generics вводили, пропадает.