Неоднозначный вызов Varargs Java
Я немного запутался в методах Java varargs:
public static int sum(int ...a) {
return 0;
}
public static double sum(double ...a) {
return 0.0;
}
Когда я пытался вызвать sum()
без передачи какого-либо аргумента, была вызвана версия метода int
. Я не понимаю, почему; обычно компилятор должен вызывать ошибку.
В отличие от этого, следующий фрагмент кода генерирует ошибку компилятора при попытке вызвать sum
без каких-либо аргументов:
public static int sum(int ...a) {
return 0;
}
public static boolean sum(boolean ...a) {
return true;
}
Ответы
Ответ 1
Общее правило, которое применяется здесь, следующее: если одна подпись метода строго конкретна, чем другая, тогда Java выбирает ее без ошибок.
Интуитивно, сигнатура метода более конкретна, если вы можете полностью удалить ее, а другая, менее конкретная, применима к каждому существующему вызову.
При представлении выбора между сигнатурами sum(int... args)
и sum(double... args)
подпись sum(int... args)
более конкретна, поскольку любое обращение этого метода также может быть передано на sum(double... args)
путем применения расширяющегося преобразования. То же самое не выполняется для метода sum(boolean... args)
, который нельзя преобразовать аналогичным образом.
Спецификация языка Java, версия SE 8:
15,12. Выражения вызова метода
Язык программирования Java использует правило выбора самого конкретного метода.
...
Один применимый метод m1 более конкретный, чем другой применимый метод m2, для вызова с выражениями аргументов e1,..., ek, если выполнено одно из следующих утверждений:
...
- m2 не является общим, а m1 и m2 применимы строгим или свободным вызовом и где m1 имеет формальные типы параметров S1,..., Sn и m2 имеет формальные типы параметров T1,..., Tn, тип Si более специфичен, чем Ti для аргумента ei для всех я (1 ≤ я ≤ n, n = k).
...
Тип S более специфичен, чем тип T для любого выражения, если S <: T (§ 4.10).
4,10. Подтипировании
double > 1 float
float > 1 long
long > 1 int
Ответ 2
Как уже упоминалось в этом ответе, при выборе метода перегрузки используется ряд правил.
Цитата:
- Примитивное расширение использует наименьший возможный аргумент метода
- Тип обтекателя не может быть расширен до другого типа Wrapper
- Вы можете вставить из int в Integer и расширить до Object, но не до Long
- Расширение бьет Бокс, Бокс побеждает Вар-Аргса.
- Вы можете вставить Box, а затем Widen (int может стать объектом через Integer)
- Вы не можете Widen, а затем Box (An int не может стать длинным)
- Вы не можете комбинировать var-args с расширением и боксом.
(Пусть переопределяет правило 1 следующим образом: "Примитивное расширение использует максимально возможный аргумент метода." )
Итак, имея в виду эти правила, мы можем получить представление о том, что происходит здесь:
Согласно правилу номер один, примитивное расширение использует максимально возможный аргумент метода. Поскольку int
представляет нечетное число (например, 1
), а double
представляется десятичным числом с точностью 32 байта больше, чем значение float
(например, 1.0
), мы можем сказать, что int
"меньше" или "меньше" double
s, и по этой логике int
можно "продвигать" до double
, а double
можно "понизить" до int
s.
Проще говоря, примитив, который может быть расширен до другого примитива (например, int
→ float
→ double
), более специфичен, чем другой. Например, int
более специфичен, чем a double
, потому что 1
может быть повышен до 1.0
.
Если вы не передали никакие аргументы этим перегруженным методам vararg с тем же именем, так как возврат фактически тот же (0 и 0.0 соответственно), компилятор предпочтет использовать метод, который принимает vararg типа int
, поскольку это более конкретно.
Итак, когда вы ввели те же методы, которые принимают в int
и boolean
(типы, которые не могут быть расширены друг к другу) соответственно, компилятор теперь не может выбрать метод для использования, поскольку int
не может быть "продвигается" или "понижается", как int
s, float
и double
s. Следовательно, это вызовет ошибку компиляции.
Надеюсь, это поможет вам понять, что происходит.