При наличии перегрузок, где один кандидат требует расширения примитивного типа, а другой — упаковки в объект, какой кандидат выберет Java?
Java выберет перегрузку, для которой достаточно расширения примитивного типа, а не ту, которая требует упаковки. Причина в поэтапном разрешении перегрузок: сначала рассматриваются вызовы без упаковки и распаковки, затем вызовы с ними, и только потом — переменное число аргументов.
Автоматическая упаковка примитивов появилась в Java 5, но уже существующий код должен был сохранять прежнее поведение. Поэтому компилятор не делает упаковку первым предпочтительным способом преобразования: ранее допустимые вызовы с расширением примитивов имеют приоритет.
Такой порядок также делает выбор перегрузки более предсказуемым. Добавление объектной перегрузки не должно неожиданно перехватывать вызовы, которые раньше разрешались через обычное преобразование примитивов.
Например, для аргумента типа int могут существовать перегрузки, принимающие long и Integer. Первый вариант требует расширения int до long, второй — упаковки int в Integer.
Если неверно считать упаковку более точным преобразованием, можно неправильно предсказать вызываемый метод. Это особенно опасно в API, где разные перегрузки имеют побочные эффекты, отличаются производительностью или обрабатывают null по-разному.
Разрешение перегруженного вызова проходит по фазам:
int в long.int в Integer или Integer в int.Поэтому при аргументе int перегрузка с параметром long побеждает перегрузку с параметром Integer: компилятор находит подходящий метод уже на первой фазе и до второй не доходит.
Здесь будет выбрана перегрузка choose(long). Литерал 1 имеет тип int, который расширяется до long; преобразование в Integer потребовало бы автоматической упаковки.
Это правило относится именно к статическому разрешению перегрузки. Оно выполняется на этапе компиляции по типам выражений, а не по фактическому типу объекта во время выполнения. Поэтому данный механизм отличается от динамического выбора переопределённого экземплярного метода.
Следует также учитывать направление преобразования. Если аргумент уже имеет тип Integer, перегрузка с Integer будет выбрана как точное совпадение. Если подходящий метод можно найти только через упаковку или распаковку, компилятор перейдёт к соответствующей следующей фазе.
В библиотеке форматирования существовали перегрузки для числового примитива и его оболочки. После добавления объектной перегрузки разработчик ожидал, что вызов с int начнёт использовать объектный путь, поскольку оболочка участвует в более «богатой» обработке. Однако фактически продолжила вызываться примитивная перегрузка.
Рассматривались три варианта. Можно было оставить обе перегрузки и документировать приоритет преобразований; это сохраняло совместимость, но усложняло понимание API. Можно было явно приводить аргументы к нужному типу; это давало контроль, но распространяло детали реализации по вызывающему коду. Третий вариант — переименовать методы, устранив перегрузку; он делал API яснее, но требовал изменений в существующих вызовах.
Выбрали сохранение перегрузок и добавили тесты на фактически вызываемый метод. Это было оправдано совместимостью: поведение вызовов с примитивами не изменилось, а спорные места получили явные приведения. Результатом стал предсказуемый выбор без массовой переработки клиентов библиотеки.
1. Что произойдёт, если аргумент имеет тип Integer, а есть перегрузки с параметрами long и Object?
Будет выбрана перегрузка с Object. Преобразование Integer в Object является расширением ссылочного типа и доступно на первой фазе, тогда как вызов метода с long требует распаковки Integer и последующего расширения примитива. Поскольку первая фаза уже нашла применимый метод, до варианта с распаковкой компилятор не переходит.
2. Может ли перегрузка с упаковкой победить перегрузку с переменным числом аргументов?
Да. Для примитивного аргумента перегрузка, требующая упаковки в один параметр-обёртку, рассматривается раньше метода с varargs. Переменное число аргументов проверяется в последней фазе, поэтому оно является менее предпочтительным механизмом.
3. Что произойдёт, если расширение примитива и упаковка ведут к разным перегрузкам, но ни одна из них не доступна без дополнительного преобразования?
Компилятор выбирает первый этап, на котором найден хотя бы один применимый метод, а внутри этого этапа применяет правила наиболее специфичного метода. Если на одном этапе остаются одинаково подходящие и несравнимые кандидаты, вызов становится неоднозначным и завершается ошибкой компиляции; Java не выбирает метод произвольно.