Java: Дженерики
Теория: Wildcard для спецификации подтипов
Дженерики позволяют нам работать однообразно с любым типом данных, но иногда возникает задача создать дженерик для определенного набора типов.
Ограничение сверху (extends)
Возьмем для примера задачу поиска среднего значения для списка чисел. Для Integer реализация метода выглядит так:
Если сделать из метода average() дженерик, то он не сработает, так как тип T будет Object, для которого операция сложения не определена.
Для подобных задач в Java есть механизм Wildcard, с его помощью можно уточнить типы, с которыми работает дженерик. В нашем случае и Integer и Double являются подтипами Number, а значит мы можем написать так: List<? extends Number>. В таком случае в метод попадут только числа, какими бы они не были, а типом параметра numbers станет List<Number>.
Обновленный код почти работает. Он все еще выдает ошибку The operator += is undefined for the argument type(s) double, Number, так как во время сложения получается что тип переменной sum это Double, а тип переменной number - Number. Эта задача решается за счет метода doubleValue() определенного у Number, который любое число преобразует в Double. Рабочий код:
Этот подход называется ограничением сверху, потому что с помощью <? extends Number> мы указываем, что тип, который может быть передан в метод, должен быть подтипом Number. Другими словами, мы задаем верхнюю границу для возможных типов, которые могут быть использованы. Это позволяет работать с любыми типами, которые являются наследниками Number (например, Integer, Double, Float и т.д.). Но при этом запрещает передачу типов, которые не входят в эту иерархию.
Ограничение снизу (super)
Мы разобрали, как Wildcard с ограничением сверху позволяет работать с типами, которые являются подтипами определенного класса или интерфейса. Это особенно полезно, когда нам нужно читать данные из коллекции, сохраняя гибкость в выборе типов. Но что, если нам нужно не только читать, но и записывать данные в коллекцию? Здесь на помощь приходит противоположный подход — ограничение снизу.
Ограничение снизу применяется, когда мы хотим добавлять элементы в коллекцию, но не хотим ограничиваться конкретным типом. Например, если у нас есть метод, который добавляет числа в список, мы можем использовать super, чтобы этот метод мог работать не только с List<Integer>, но и с List<Number> или даже List<Object>.
Рассмотрим пример:
Метод addNumbers() принимает список, который может содержать элементы типа Integer или его супертипы (например, Number или Object). Таким образом, мы ограничиваем тип снизу. Это позволяет добавлять целые числа в список, даже если он объявлен как List<Number> или List<Object>
Здесь мы передаем список List<Number> в метод addNumbers(), и он успешно добавляет в него целые числа
Принцип PECS
Чтобы лучше понять, когда использовать extends а когда super, полезно запомнить принцип PECS (Producer Extends, Consumer Super). Этот принцип помогает определить, какой тип Wildcard следует использовать в зависимости от роли коллекции.
Если коллекция является источником данных (производителем, producer), используйте extends. Например, List<? extends Number> подходит для чтения чисел из списка.
Если вы записываете данные в коллекцию, то коллекция является приемником данных (потребителем, consumer). В этом случае используйте super. Например, List<? super Integer> подходит для добавления целых чисел в список
Wildcard с ограничениями не часто используется в прикладном коде, но часто используется в библиотеках. Поэтому его нужно знать на начальном этапе, хотя бы для того, чтобы читать документацию и понимать что там написано.
Например, вот как выглядит определение метода Collections.copy()
Здесь dest — это список, который может содержать элементы типа T или его супертипы (потребитель), а src — это список, который может содержать элементы типа T или его подтипы (производитель).
Выводы
- Wildcard с ограничениями
extendsиsuperделает Generics в Java более гибкими и безопасными. - Используя
extends, мы можем работать с коллекциями, которые производят данные - C помощью
superмы можем работать с коллекциями, которые потребляют данные - Принцип PECS помогает запомнить, когда и какой тип ограничения использовать
Для более глубокого понимания темы мы рекомендуем просмотреть видео лекцию, которая является дополнительным материалом к данному курсу:
!vimeo!(818921329)



