Продвижение типа Java в параметрах - PullRequest
20 голосов
/ 15 октября 2019

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

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);
    }
}

Ответы [ 3 ]

17 голосов
/ 15 октября 2019

Я думаю, что это как-то связано со специфическим правилом 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) без ошибок компиляции.

7 голосов
/ 15 октября 2019

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

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

  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 (<a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.10" rel="nofollow noreferrer"> §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.

0 голосов
/ 15 октября 2019

потому что значение 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

...