Продвижение Java-типов в параметрах

20

Я наткнулся на этот фрагмент:

public class ParamTest {
    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(long a, long b) {
        System.out.println("In long " + (a + b));
    }

    public static void printSum(double a, long b) {
        System.out.println("In doubleLONG " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}

Это приведет к ошибке компиляции:

Ошибка: (15, 9) java: ссылка на printSum неоднозначна, как метод printSum (int, double) в ParamTest, так и метод printSum (long, long) в ParamTest match

Как это неоднозначно? Разве в этом случае не следует продвигать только второй параметр, поскольку первый параметр уже является int? Первый параметр не нужно продвигать в этом случае правильно?

Компиляция завершится успешно, если я обновлю код для добавления другого метода:

public static void printSum(int a, long b) {
    System.out.println(String.format("%s, %s ", a, b));
}

Позвольте мне расширить, чтобы уточнить. Код ниже приводит к двусмысленности:

public class ParamTest {

    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(long a, long b) {
        System.out.println("In long " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}

Тогда этот код ниже также приводит к неоднозначности:

public class ParamTest {

    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(double a, long b) {
        System.out.println("In doubleLONG " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}

Однако это не приводит к двусмысленности:

public class ParamTest {

    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(long a, double b) {
        System.out.println("In longDBL " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}
riruzen
источник
2
Компилятор может отобразить ваш вызов printSum (1, 2); или printSum (long a, long b) или printSum (int a, double b), следовательно, это неоднозначно. Вы должны явно помочь компилятору сделать выбор, указав тип точно так: printSum (1, 2d)
Ravindra Ranwala
6
Вы неверно процитировали сообщение об ошибке, и разница очень важна. Фактическое сообщение об ошибке: Error:(15, 9) java: reference to printSum is ambiguous both method printSum(int,double) in ParamTest and method printSum(long,long) in ParamTest match- это не метод, который является неоднозначным, это вызов метода, который является неоднозначным.
Эрвин Болвидт
1
@ErwinBolwidt сообщение об ошибке было от затмения извините, что я там неправильно цитировал. В любом случае, я до сих пор не понимаю, потому что добавление printSum (int a, long b) удаляет сообщение об ошибке.
Rruzen
2
JLS-5.3 => Если тип выражения не может быть преобразован в тип параметра с помощью преобразования, разрешенного в свободном контексте вызова, тогда происходит ошибка времени компиляции. кажется применимым к контексту, но на самом деле не так просто сделать вывод, как это сделать. +1
Наман
1
Нам определенно нужен канонический вопрос для этого: stackoverflow.com/… . "
Marco13

Ответы:

17

Я думаю, что это как-то связано со специфическим правилом JLS о 15.12.2.5. Выбор наиболее специфического метода . В нем говорится, что:

Если более одного метода-члена доступны и применимы к вызову метода, необходимо выбрать один, чтобы предоставить дескриптор для отправки метода во время выполнения. Язык программирования Java использует правило, согласно которому выбирается наиболее конкретный метод.

Как Java выбирает наиболее конкретный метод, более подробно объясняется текстом:

Неформальная интуиция заключается в том, что один метод более специфичен, чем другой, если любой вызов, обработанный первым методом, может быть передан другому без ошибки времени компиляции. В таких случаях, как явно заданный аргумент лямбда-выражения (§15.27.1) или вызов переменной arity (§15.12.2.4), допускается некоторая гибкость для адаптации одной подписи к другой.

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

Для этих методов ни один из них не может быть определен как более конкретный:

public static void printSum(int a, double b) {
    System.out.println("In intDBL " + (a + b));
} // int, double cannot be passed to long, long or double, long without error

public static void printSum(long a, long b) {
    System.out.println("In long " + (a + b));
} // long , long cannot be passed to int, double or double, long without error

public static void printSum(double a, long b) {
    System.out.println("In doubleLONG " + (a + b));
} // double, long cannot be passed to int, double or long, long without error

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

public static void printSum(int a, long b) {
    System.out.println(String.format("%s, %s ", a, b));
}

То есть (int, long) может быть передано (int, double), (long, long) или (double, long) без ошибок компиляции.

бунт
источник
2
Таким образом, чтобы подвести итог, если все методы доступны посредством вызова, тогда java идентифицирует метод, который можно вызвать к другим методам без ошибки времени компиляции, если найден, то использовать это как наиболее конкретное и вызвать его в противном случае ошибку неоднозначности? Я думаю, это правильный ответ @riruzen.
Сандип Кокате
Спасибо! Я попробовал это с (double, int) vs (double, long), и это действительно прояснило неоднозначность, тогда (double, int) vs (long, double) оказалось неоднозначным, как и ожидалось.
Rruzen
7

Это действительно очень интересный вопрос. Давайте шаг за шагом пройдемся по Спецификации языка Java.

  1. Когда компилятор пытается определить потенциально применимые методы, первое, что он делает, - поиск методов, применимых строгим вызовом .

  2. В вашем случае таких методов нет, поэтому следующий шаг - найти методы, применимые Loose Invocation

  3. В этот момент все методы совпадают, поэтому наиболее конкретный метод ( §15.12.2.5 ) выбирается среди методов, которые применимы посредством свободного вызова.

Это ключевой момент, поэтому давайте посмотрим на это внимательно.

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

(Нас интересует только следующий случай):

  • m2 не является универсальным, и m1 и m2 применимы при строгом или произвольном вызове, и где m1 имеет формальные типы параметров S1, ..., Sn и m2 имеет формальные типы параметров T1, ..., Tn, тип Si более специфичнее чем Ti для аргумента ei для всех i (1 ≤ i ≤ n, n = k).

Проще говоря, метод более конкретен, если все его типы параметров более специфичны . А также

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

Выражение S <: Tозначает, что Sэто подтип T. Для примитивов у нас есть следующие отношения:

double > float > long > int

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

public static void printSum(int a, double b) {  // method 1
    System.out.println("In intDBL " + (a + b));
}

public static void printSum(double a, long b) { // method 2
    System.out.println("In doubleLONG " + (a + b));
}

В этом примере первый параметр метода 1, очевидно, более специфичен, чем первый параметр метода 2 (если вы вызываете их с целочисленными значениями:) printSum(1, 2). Но второй параметр является более специфичным для метода 2 , так как long < double. Поэтому ни один из этих методов не является более конкретным, чем другой. Вот почему у вас есть двусмысленность здесь.

В следующем примере:

public static void printSum(int a, double b) { // method 1
    System.out.println("In intDBL " + (a + b));
}

public static void printSum(long a, double b) { // method 2
    System.out.println("In longDBL " + (a + b));
}

первый тип параметра метода 1 более специфичен, чем в методе 2, поскольку int < longвторой тип параметра для них одинаков, поэтому метод 1 выбран.

Кирилл Симонов
источник
Я не понизил ваш ответ, но этот ответ не объясняет, почему (int, double) неоднозначно с (long, long), поскольку ясно, что (int, double) должно быть более конкретным по отношению к первому параметру.
Rruzen
3
@riruzen это объясняет: doubleне более конкретно, чем long. А для выбора метода все параметры типа должны быть более конкретными: тип Si более специфичен, чем Ti, для аргумента ei для всех i (1 ≤ i ≤ n, n = k)
Кирилл Симонов
Это должно быть до +1
чай
0

потому что значение int также может рассматриваться как двойной в Java. Средство double a = 3действительно и то же самое с длинным. long b = 3Вот почему оно создает двусмысленность. Ты звонишь

printSum(1, 2);

Запутывает все три метода, потому что все эти три действительны:

int a = 1;
double b =1;
long c = 1;

Вы можете поставить L в конце, чтобы указать, что это длинное значение. например:

printSum(1L, 2L);

для двойного вам нужно конвертировать его:

printSum((double)1, 2L);

также прочитайте комментарий @Erwin Bolwidt

Сандип Кокате
источник
Да, компилятор запутался во всех 3 методах, сначала компилятор ищет точный тип, а если он не обнаружил ни одного, попытайтесь найти совместимый тип, и в этом случае все они совместимы.
Другой кодер
2
Если у меня есть 4 объявления методов вместо 3, неоднозначность исчезнет: public static void printSum (int a, double b) public static void printSum (int a, long b) public static void printSum (long a, long b) public static void printSum (double a, long b), поэтому, хотя я согласен с вашим ответом, он все равно не отвечает на вопрос. Java делает автоматическое продвижение типов, если точный тип не доступен, если я не ошибаюсь. Я просто не понимаю, почему это становится двусмысленным с этими 3.
riruzen