Какой рациональный подход к использованию generic в Java
Что проверяют
Не определение обобщений, а умение применять их в API без лишней сложности: где нужен параметр типа, где wildcard, а где generics только мешают. Почти всегда следом спрашивают про стирание типов и про то, почему List<Integer> не подтип List<Number>.
Как отвечать
Каркас из нескольких правил, каждое с причиной:
- без raw types:
Listбез параметра отключает проверки компилятора, иClassCastExceptionвылезает далеко от места ошибки. Сырые типы остались только ради совместимости с кодом до Java 5; - PECS: producer
extends, consumersuper. Метод, который читает из коллекции, принимаетList<? extends Number>, а метод, который пишет, принимаетList<? super Integer>. Так устроенCollections.copy(List<? super T> dest, List<? extends T> src); - параметр типа, только когда он связывает сигнатуру: если
Tвстречается один раз, хватит wildcard. В<T extends Comparable<? super T>> T max(List<? extends T> list)параметр оправдан, он связывает вход и выход; - wildcard не в возвращаемом типе: вызывающему коду придётся разбираться с
List<?>; - стирание типов: в рантайме параметров нет, поэтому нельзя
new T(),instanceof List<String>иnew T[]. Обход черезClass<T>илиSupplier<T>; - varargs: обобщённые varargs дают heap pollution, безопасные методы помечают
@SafeVarargs; - примитивы:
List<int>не существует, есть автоупаковка и её цена на больших объёмах, для чисел удобнееIntStream.
И мера: generic-класс с четырьмя параметрами ради «универсального сервиса» читать тяжелее, чем два конкретных класса.
Где спотыкаются
Считают, что раз Integer наследует Number, то List<Integer> можно передать туда, где ждут List<Number>. Generics инвариантны, ковариантность даёт только ? extends.
Глушат @SuppressWarnings("unchecked") на весь класс вместо одной строки с пояснением, почему приведение безопасно.
Пытаются добавить элемент в List<? extends Number> и удивляются ошибке компиляции: такой список только для чтения, записать туда можно разве что null.