Generics позволяют создавать классы, интерфейсы и методы с параметрами типов, что обеспечивает проверку типа на этапе компиляции и помогает избежать ClassCastException.
Ключевые особенности и подводные камни:
new List<String>[10] — ошибка компиляции.T obj = new T();if(obj instanceof List<String>) — ошибка.? extends T — ковариантность (чтение), ? super T — контравариантность (запись).Пример:
// Ковариантный подход для чтения void printNumbers(List<? extends Number> numbers) { for (Number n : numbers) { System.out.println(n); } } // Контравариантный подход для записи void addIntegers(List<? super Integer> list) { list.add(10); }
Вопрос: "Чем отличаются List<Object> и List<?>? Можно ли положить любой объект в List<?>?"
Ответ: Нет, в List<?> нельзя ничего добавить (кроме null), потому что компилятор не знает, какой именно там параметр типа. А в List<Object> можно добавлять любые объекты.
Пример:
List<?> list1 = new ArrayList<String>(); // list1.add("test"); // Ошибка компиляции! List<Object> list2 = new ArrayList<>(); list2.add("test"); // OK
История
Команда разработчиков попыталась реализовать кэш на основе массива параметризованного типа
T[]. Из-за type erasure и невозможности создавать массивы generic-типа решение не работало как ожидалось: получался массивObject[], приводящий к ClassCastException при runtime-кастах.
История
В одном из микросервисов разработчик пытался реализовать ресивер, использующий List<?> как параметр, и пытался модифицировать коллекцию. Это вызвало ошибку компиляции и затяжку сроков релиза, поскольку пришлось рефакторить логику с учётом PECS.
История
На проекте по интеграции с внешней системой разработчик сделал ошибку, затерев коллекцию одного типа другой через необработанный raw-type: List list = new ArrayList<String>(), что привело к ClassCastException и падениям сервиса на production при попытке кастовать элементы к другим типам.