Розширення типу 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

Як це неоднозначно? Чи не слід в цьому випадку просувати лише другий параметр, оскільки перший параметр вже є цілим? Перший парам в цьому випадку не потрібно просувати?

Компіляція успішна, якщо я оновлюю код, щоб додати інший метод:

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

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, довгий b) видаляє повідомлення про помилку.
riruzen

2
JLS-5.3 => Якщо тип виразу не може бути перетворений у тип параметра шляхом перетворення, дозволеного у вільному контексті виклику, виникає помилка часу компіляції. Здається, застосовний до контексту, але насправді не можна зрозуміти як. +1
Наман

1
Ми , безумовно , необхідно канонічне питання для цього: stackoverflow.com / ... «.
Marco13

Відповіді:


17

Я думаю, що це має щось спільне з конкретним правилом JLS про 15.12.2.5. Вибір найбільш конкретного методу . У ньому зазначається, що:

Якщо для виклику методу доступний і застосовний більш ніж один член-метод, необхідно вибрати один, щоб надати дескриптор для відправки методу виконання. Мова програмування Java використовує правило, що обирається найбільш конкретний метод.

Як Java вибирає найбільш конкретний метод , далі пояснюється текстом:

Неофіційна інтуїція полягає в тому, що один метод більш специфічний, ніж інший, якщо будь-яке виклик, оброблений першим методом, може бути передано іншому без помилки часу компіляції. У таких випадках, як явно набраний аргумент лямбда-вираження (§15.27.1) або виклик змінної арності (§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.
Sandeep Kokate

Дякую! Я спробував це з (double, int) vs (double, long), і це дійсно вияснило неоднозначність, тоді (double, int) vs (long, double) було неоднозначним, як очікувалося.
riruzen

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) має бути більш конкретним щодо першого параметра.
riruzen

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
Якщо у мене замість 3 оголошень 4 методу, неоднозначність вимикається: 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 (подвійний a, довгий b), тому, хоча я згоден з вашою відповіддю, він все ще не відповідає на питання. Java робить автоматичне просування типу, якщо точний тип недоступний, якщо я не помиляюся. Я просто не розумію, чому це стає неоднозначним із цими 3.
riruzen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.