Как Java обрабатывает потенциально неоднозначные вызовы методов? - PullRequest
0 голосов
/ 01 июля 2018

Играя с неоднозначными вызовами методов, которые имеют несколько параметров, я заметил, что довольно часто это не было на самом деле неоднозначно, когда я ожидал, что это привело к некоторому странному поведению, которое я не мог полностью понять.

Например, со следующей структурой наследования:

public static class A {
}

public static class B extends A {

}
public static class C extends B {

}

И работает метод test().

public static void test() {
    test(new C(), new C(), new C());
}

Почему-то эти два метода неоднозначны

public static void test(A x, A xx, B xxx) {
    System.out.println("TEST 1");
}

public static void test(A x, C xx, A xxx) {
    System.out.println("TEST 2");
}

Однако, если поменять два последних аргумента во втором методе, этот приоритет будет иметь приоритет.

public static void test(A x, A xx, B xxx) {
    System.out.println("TEST 1");
}

public static void test(A x, A xx, C xxx) {
    System.out.println("TEST 2"); //No longer ambiguous, this one is called
}

Может ли кто-нибудь объяснить это поведение, а также вообще, как именно квазиоднозначные вызовы методов определяются в Java с несколькими параметрами?

Ответы [ 4 ]

0 голосов
/ 01 июля 2018

Я пытаюсь объяснить вам, в чем будет проблема.

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

Эти методы:

  • long mul (int x, long y)
  • long mul (long x, int y)
  • long mul (long x, long y)

Потому что перегрузка всех этих методов допустима и вычисляет одно и то же: результат умножения между x и y.

Предположим, есть две переменные, которые являются параметрами для метода:

int x;
long y;
  • Если я вызову mul (x, y), я получу x * y в качестве результата, потому что mul (int, long) является наиболее конкретный метод для этих параметров.
  • Если я вызову mul ((long) x, y), я получу снова x * y, потому что mul (long, long) является наиболее специфичным методом для этих параметров.
  • Вместо этого, если я вызову mul (x, x), я не получу x * x, потому что язык (Java) не может угадать, что является лучшим приведением для параметров.

Вызов mul (x, x) приводит к этой дилемме: я должен приводить x к long, поэтому вызывать mul (long, long) или я должен приводить y к int, поэтому вызывать mul (int, int).

Это проблема, которую вы можете найти при использовании перегрузки.

Надеюсь, я вам помогу!

0 голосов
/ 01 июля 2018

Это потому, что оба метода тестирования могут принимать экземпляры C в качестве аргументов, а C может действовать как B и A

0 голосов
/ 01 июля 2018

Прочитав главу «Спецификации языка Java» на при выборе наиболее конкретного метода , важно отметить следующие утверждения:

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

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

В основном это означает, что при наличии двух (не varargs) сигнатур методов, которые могут соответствовать вызову метода, компилятор выберет более конкретный метод, а более конкретный метод - тот, где каждый параметр более специфичен, чем (т.е. либо тот же класс, что и подкласс) соответствующего параметра в другой сигнатуре (здесь для простоты игнорируются обобщения).

Так, например, когда у вас есть (A, A, B) и (A, A, C), последний более конкретен, потому что A = A и C является подклассом B, поэтому выбор ясен и однозначен .

Но когда у вас есть (A, A, B) и (A, C, A), первое не может быть более конкретным, потому что C является подклассом A, но последнее также не может быть более конкретным, потому что B является подклассом A. Следовательно, неоднозначность.

0 голосов
/ 01 июля 2018
public static void test(A x, A xx, B xxx) {
    System.out.println("TEST 1");
}

public static void test(A x, C xx, A xxx) {
    System.out.println("TEST 2");
}

Проблема здесь в том, что оба метода применяются к (A, C, B). В этом конкретном случае присутствует неопределенность.

В представленном вами однозначном примере нет общей подписи для обоих объявлений:

public static void test(A x, A xx, B xxx) {
    System.out.println("TEST 1");
}

public static void test(A x, A xx, C xxx) {
    System.out.println("TEST 2"); //No longer ambiguous, this one is called
}

Первое относится к (A, A, B), второе относится к (A, A, C). Второй просто переопределяет первый, предоставляя более конкретный вариант использования (подпись). Возможно, вы захотите считать это переопределением первого, хотя это, вероятно, не технический термин.

По расширению C - это A, поэтому при выборе метода вызова, где параметр равен C или A, интерпретатор вызовет либо (следовательно, может возникнуть неоднозначность), но должен вызвать тот, у которого больше конкретный параметр, если имеется. Это может быть описано как полиморфизм; тип времени выполнения экземпляра используется для определения того, что классы и вызовы методов ищутся из класса-потомка.

Как указывает Дж. Б. Низет, языковые спецификации являются здесь авторитетом, но я тоже люблю экспериментировать.

...