Перегрузка метода переменными аргументами (varargs)

Я удивлен, увидев вывод этого кода:

public class File
{
    public static void main(String[] args)
    {
        movie();
    }

    static void movie(double... x)
    {
        System.out.println("No varargs");
    }

    static void movie(int... x)
    {
        System.out.println("One argument");
    }
}

Он выводит,

One argument

Почему это так?

Я думал, что этот код не будет компилироваться, потому что вызов movie() является неоднозначным, но он отлично работает и выдает One argument.

Если я изменю код на:

public class File
{
    public static void main(String[] args)
    {
        movie();
    }

    static void movie(boolean... x)  //Changed the parameter type to boolean from double
    {
        System.out.println("No varargs");
    }

    static void movie(int... x)
    {
        System.out.println("One argument");
    }
}

Появляется сообщение об ошибке.

Почему первый код работает нормально, а второй дает ошибку?

Ответы

Ответ 1

Это связано с тем, что int более специфичен, чем double, в то время как сравнение между int и boolean отсутствует.

Как указано в разделе JLS 15.12.2.5 (выделено мной):

Один применимый метод m1 более конкретный, чем другой применимый метод m2, для вызова с выражениями аргументов e1,..., ek, если выполнено одно из следующих утверждений:

  • ...
  • m2 не является общим, а m1 и m2 применимы при вызове переменной arity, и где первые k переменных параметров arty-параметров m1 являются S1,..., Sk и первой переменной k типы параметров arty m2 являются T1,..., Tk, тип Si более специфичен, чем Ti для аргумента ei для всех я (1 ≤ я ≤ k). Кроме того, если m2 имеет k + 1 параметров, то k + 1'th тип параметра переменной arity m1 является подтипом k + 1'th переменной параметра arity параметра m2.

Что более конкретное на самом деле означает, позже определяется подтипирование:

Тип S более специфичен, чем тип T для любого выражения, если S <: T.

Это означает, что S более конкретный, чем T is S является подтипом T. Для примитивных типов это сводится к следующим свойствам:

  • double > float
  • float > long
  • long > int
  • int > char
  • int > short
  • short > byte

Обратите внимание, что boolean не существует.

Таким образом,

public static void main(String[] args) {
    movie();
}

static void movie(int... x) { }
static void movie(short... x) { }
static void movie(double... x) { }
static void movie(byte... x) { }

компилируется и movie(byte... x) будет вызываться, потому что он является наиболее конкретным.

Однако

public static void main(String[] args) {
    movie();
}

static void movie(int... x) { }
static void movie(boolean... x) { }

не компилируется, потому что boolean нельзя сравнить с int.

Ответ 2

Неформальная интуиция заключается в том, что один метод более конкретный, чем другой , если может быть передано любое обращение, обработанное первым методом на другой без ошибки времени компиляции.

Рассмотрим movie(int...x) как M1 и movie(double...x) как M2.

Метод M1 более специфичен, чем M2, потому что мы можем вызывать метод M2 с теми же входами, что и метод M1, без ошибок времени компиляции.

Таким образом, вызов первого метода M1 определенно обрабатывается M2. Поскольку double может обрабатывать int без каких-либо проблем.

Но мы не можем вызывать M1 с теми же входами, которые даны методу M2, и это легко понять.

Проверьте следующий пример,

public class Test {

    public static void main(String[] args) {
        movie();
    }

    static void movie(int... x) {
        System.out.println("One argument");
    }

    static void movie(short... x) {
        System.out.println("Short argument");
    }
}

OUTPUT

Short argument

Потому что здесь short более конкретный, чем int для вызова метода movie().


С другой стороны, для метода boolean вызов movie(); вызывает путаницу, потому что компилятор не может решить, какой метод вызывать, потому что в этом случае нет такой точки более конкретного метода.

Ответ 3

Я думаю, что причина заключается в автоматическом продвижении типа в java. По умолчанию тип выражения продвигается до типа Integer или Long (в зависимости от диапазона), если ваше выражение не содержит с плавающей запятой.

Итак, в первом случае выражение void просто разрешено для int varargs, поскольку Integer выигрывает конкурс из-за отсутствия значения с плавающей запятой в выражении.

Во втором случае компилятор не может решить, что вызывать, то есть из перегруженных методов он не может решить, какой из них вызывать без аргументов.

Ответ 4

Ответ:

1. Если мы используем объекты типов данных, такие как int как Integer, он также дает ту же ошибку. Поэтому причина в том, что он не работает с типами объектов и дает ошибки, если для функции не задан аргумент.

2. Поскольку vararg принимает аргумент как массив заданного типа. Поэтому, если вы не передаете никаких аргументов, он принимает его как массив с нулевыми аргументами и выполняет целочисленную функцию, потому что он получил одну из функций с целым аргументом.

3. Это когда вы добавляете float и double для двух разных функций и не передаете никакого аргумента, тогда он выполняет функцию аргумента float.

Решение:

  • Не используйте типы объектов, такие как String, Integer, Float, Double, Boolean и Char. Вместо этого используйте int, float, double и boolean, если хотите для выполнения одной из функций без передачи аргументов.
  • Итак, в данном примере нам нужно указать логический аргумент для логического объекта не-примитивного типа данных true или false.
public class File
{
    public static void main(String[] args)
    {
        movie(true);
    }

    static void movie(Boolean...x)  //Changed the parameter type to boolean from double
    {
        System.out.println("No varargs"+x);
    }

    static void movie(int...x)
    {
         System.out.println("One argument");
    }
}