При передаче аргументов разных подтипов в обобщённый метод с одним параметром T каким образом компилятор выбирает тип T?
Компилятор выводит такой тип T, которому оба аргумента могут быть присвоены. Если аргументы имеют разные типы, T обычно становится их наиболее специальным общим типом, а не типом одного из аргументов.
Например, для Integer и Double подходящим результатом может быть Number. При этом фактически выведенный тип иногда представляет собой пересечение нескольких типов и не всегда имеет простое имя в исходном коде.
Обобщения появились в Java 5, чтобы проверять совместимость типов на этапе компиляции и сократить необходимость ручных приведений. При этом Java сохранила совместимость с существующим кодом и реализовала generics через стирание типов.
Чтобы обобщённые методы оставались удобными, компилятор получил механизм вывода аргументов типа. Благодаря ему вызывающему коду обычно не нужно явно указывать T.
Один параметр типа может участвовать сразу в нескольких позициях метода. Каждый аргумент предъявляет ограничение: его тип должен быть совместим с предполагаемым T.
Если передать значения разных типов, выбор только одного из них сделал бы вызов слишком узким. Выбор Object всегда был бы безопасным, но часто терял бы полезную типовую информацию. Поэтому компилятор ищет наиболее специальный тип, удовлетворяющий всем ограничениям.
Рассмотрим обобщённый метод:
Для первого аргумента возникает требование, что Integer должен быть совместим с T; для второго — что Double должен быть совместим с T. Тип Integer не подходит как T, потому что Double нельзя передать туда. Аналогично, Double не подходит для Integer.
Number удовлетворяет обоим ограничениям, поэтому вызов корректен. Инвариантность обобщённых типов здесь не нарушается: компилятор не считает List<Integer> разновидностью List<Number>, а выводит единственный тип T, которому отдельные аргументы соответствуют напрямую.
Результат вывода не обязан совпадать с очевидным именованным классом. Если значения реализуют несколько общих интерфейсов, компилятор может сформировать пересечение типов. Такой тип способен сохранять больше ограничений, чем один общий класс, но его точное представление может быть недоступно программисту как отдельное имя.
Вывод также может зависеть от контекста назначения. Ожидаемый тип результата способен добавить ограничение на T, если вызов находится в контексте, где целевой тип участвует в выводе. Поэтому один и тот же метод иногда выводит разные типы при присваивании результату переменных разных типов.
Явное указание аргумента типа заменяет автоматический поиск конкретным требованием. Компилятор всё равно проверяет, что переданные аргументы совместимы с выбранным T; явная типизация не отключает проверку ограничений.
В библиотеке есть метод, возвращающий один из двух кандидатов. Вызов с двумя значениями разных числовых обёрток должен сохранить возможность работать с результатом как с числом.
Вариант с возвращаемым типом Object принимает любые значения, но заставляет клиента вручную приводить результат и скрывает ошибку до выполнения. Вариант с отдельными перегруженными методами для каждой пары типов плохо масштабируется: число перегрузок быстро растёт, а новые типы требуют изменения API.
Обобщённый метод с одним T позволяет компилятору вывести общий совместимый тип для конкретного вызова. В результате клиент получает проверяемый на этапе компиляции результат, а API остаётся компактным. Если нужна более точная специализация, вызывающий код может задать ожидаемый тип или явно указать аргумент типа.
Нет. Компилятор учитывает не только классы, но и общие интерфейсы, ограничения параметров и целевой тип выражения. Поэтому фактический результат может быть пересечением типов, а не просто Object или ближайшим общим суперклассом.
Инвариантность относится к отношениям между параметризованными типами, например между List<Integer> и List<Number>. В рассматриваемом вызове аргументы передаются непосредственно в параметры типа T; компилятор подбирает такой T, чтобы каждый отдельный аргумент был ему присваиваем. Отношение между List<Integer> и List<Number> при этом вообще не формируется.
Да. Тип можно задать явно либо создать контекст назначения, требующий этого типа. Такой выбор допустим только при сохранении совместимости: каждый аргумент должен подходить выбранному T, иначе компилятор отвергнет вызов. Принудительный выбор более общего типа обычно снижает точность результата, но может быть полезен для согласования нескольких операций в одном выражении.