Для чего нужны дженерики в Java?
Что проверяют
Понимаете ли вы, что дженерики это проверка на этапе компиляции, и знаете ли про стирание типов. Второе спрашивают почти всегда следом.
Как отвечать
Дженерики позволяют параметризовать классы, интерфейсы и методы типом. Пользы две: ошибки ловятся компилятором, а приведения типов исчезают из кода.
List<String> titles = new ArrayList<>();
titles.add(42); // ошибка компиляции, а не ClassCastException в проде
String first = titles.get(0); // без приведенияДальше главное, что отличает уверенный ответ, это стирание типов: параметр типа существует только на этапе компиляции, в байт-коде его нет. Отсюда практические ограничения:
- нельзя написать
new T()иnew T[10]; - нельзя проверить
x instanceof List<String>; - перегрузка по
List<String>иList<Integer>невозможна, это один и тот же метод после стирания; - зато сохраняется совместимость со старым кодом без дженериков, ради чего стирание и выбрали.
Стоит назвать и подстановочные знаки с правилом «producer extends, consumer super»: List<? extends Number> можно читать, но нельзя добавлять; List<? super Integer> наоборот. Это то, из-за чего сигнатуры вроде Collections.copy выглядят сложно, но иначе безопасность типов не выразить.
Полезно добавить: массивы ковариантны, а дженерики инвариантны, поэтому Object[] arr = new String[1] компилируется и падает в рантайме, а аналогичный код со списками не скомпилируется вовсе.
Где спотыкаются
Не могут объяснить стирание и удивляются, почему List<String> и List<Integer> это один класс.
Используют сырой тип List вместо List<T> и теряют все проверки, получая предупреждение о непроверяемой операции.
Путают ковариантность массивов и инвариантность дженериков.