Сравните параметр типа с верхней границей и wildcard с верхней границей: какой вариант сохраняет в результате метода конкретный тип аргумента?
Параметр типа с верхней границей сохраняет связь между типом аргумента и типом результата. Поэтому для метода, который должен вернуть тот же конкретный подтип, следует использовать параметр типа, например T extends Number, а не только ? extends Number.
Wildcard описывает неизвестный тип, подходящий под ограничение. Из коллекции List<? extends Number> безопасно читать как Number, но сам конкретный подтип наружу не извлекается.
Generics появились в Java 5, чтобы добавить проверку типов на этапе компиляции и уменьшить необходимость явных приведений типов. При этом Java сохраняла совместимость с существующим кодом, поэтому обобщения реализованы через стирание типов, а не через сохранение параметров типов в объектах во время выполнения.
Параметры типа и wildcards решают разные задачи проектирования API. Параметр типа выражает именованную связь между несколькими частями сигнатуры, а wildcard обозначает неизвестный тип, который важен только в пределах конкретного использования.
Представим метод, принимающий список чисел и возвращающий его первый элемент. Если метод объявлен с ? extends Number, результат имеет статический тип Number, даже когда фактически передан список Integer.
Это безопасно, но может привести к потере информации в API: вызывающему коду придётся выполнять приведение или работать с более общим типом. Если же требуется сохранить конкретный тип элемента, wildcard выбирает слишком слабое выражение связи между аргументом и результатом.
Параметр типа вводит имя типа, которое можно использовать в нескольких местах сигнатуры:
Для List<Integer> вызов first возвращает Integer: параметр T связывает тип элементов списка с типом результата. В firstAny неизвестный тип захвачен компилятором, но наружу метод объявляет только Number, поэтому вызывающий код получает именно этот тип.
Ограничение T extends Number означает, что T обязан быть Number или его подтипом. Это не означает, что внутри метода можно создать объект T: верхняя граница даёт сведения о доступных операциях и совместимости типов, но не предоставляет конструктор конкретного подтипа.
? extends Number удобно, когда метод только читает значения и конкретный тип не нужно возвращать или связывать с другим параметром. Такой параметр нельзя безопасно пополнять произвольным Number, потому что фактический список может быть List<Integer> или List<Double>.
Главный компромисс такой: параметр типа делает сигнатуру более выразительной и сохраняет типовую связь, но иногда усложняет вывод типов и API. Wildcard проще выражает независимый неизвестный тип и часто лучше подходит для потребляющих или производящих коллекций, однако может скрыть тип, который нужно передать дальше.
В библиотеке обработки метрик был метод, принимающий список числовых значений и возвращающий первый элемент для дальнейшей обработки. Рассматривались три варианта: принимать List<? extends Number> и возвращать Number, использовать сырой List или ввести <T extends Number>.
Первый вариант был безопасным, но заставлял клиентов терять конкретный тип: результат списка Long становился Number. Сырой тип сохранял гибкость только ценой отключения части проверок компилятора и возможных ошибок во время выполнения.
Выбрали <T extends Number>, поскольку библиотека должна была вернуть значение того же типа, что и элементы входного списка. В результате вызовы для Integer, Long и BigDecimal сохраняли соответствующие статические типы без небезопасных приведений.
T на wildcard и сохранить тот же тип результата?Нет, не в общей сигнатуре без дополнительного параметра типа. List<? extends Number> сообщает только, что элементы имеют некоторый неизвестный тип, совместимый с Number; стандартный результат чтения из такой коллекции имеет тип Number. Для сохранения конкретного типа нужно связать вход и выход через именованный параметр, например T.
T extends Number не позволяет добавить любой Number в List<T>?Потому что фактический T может быть узким подтипом. Если T равен Integer, добавление Double нарушило бы типобезопасность. Верхняя граница ограничивает возможные значения T, но не превращает T в сам тип границы и не разрешает принимать любой объект этой границы.
<T extends Number> и не использовать T в результате?Связь с конкретным результатом не появится автоматически. Если T используется только во входном параметре, он может помогать проверять совместимость аргументов, но возвращаемый тип всё равно определяется отдельной частью сигнатуры. Параметр типа следует вводить тогда, когда одна и та же типовая переменная действительно связывает несколько позиций API; иначе wildcard обычно проще и точнее выражает намерение.