Почему автобокс делает некоторые вызовы неоднозначными в Java? - PullRequest
33 голосов
/ 01 февраля 2009

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

public class Test {
    static void f(Object a, boolean b) {}
    static void f(Object a, Object b) {}

    static void m(int a, boolean b) { f(a,b); }
}

При компиляции вызывает следующую ошибку:

Test.java:5: reference to f is ambiguous, both method
    f(java.lang.Object,boolean) in Test and method
    f(java.lang.Object,java.lang.Object) in Test match

static void m(int a, boolean b) { f(a, b); }
                                  ^

Исправление этой ошибки тривиально: просто используйте явный автобокс:

static void m(int a, boolean b) { f((Object)a, b); }

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

Так почему же не удалось разрешить перегрузку? Почему компилятор не автоматически блокирует первый аргумент и не принимает второй аргумент нормально? Почему я должен был явно запросить автобокс?

Ответы [ 6 ]

33 голосов
/ 01 февраля 2009

Когда вы сами приводите первый аргумент к Object, компилятор сопоставляет метод без использования автобоксирования (JLS3 15.12.2):

Первый этап (§15.12.2.2) выполняет разрешение перегрузки без разрешения бокс или распаковка конверсии, или использование метода переменной арности призывание. Если нет применимого метода найден на этом этапе, то обработка продолжается до второго фазы.

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

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

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

Кроме того: компилятор не всегда может выбрать наиболее конкретный метод, исходя из необходимого количества автоматических (не) упаковок. Это все еще может привести к неоднозначным случаям. Например, это все еще неоднозначно:

public class Test {
    static void f(Object a, boolean b) {}
    static void f(int a, Object b) {}

    static void m(int a, boolean b) { f(a, b); } // ambiguous
}

Помните, что алгоритм выбора метода сопоставления (шаг 2 времени компиляции) исправлен и описан в JLS. На втором этапе выборочная автобокс или распаковка отсутствует. Компилятор найдет все методы, которые доступны (оба метода в этих случаях) и применимы (опять же, два метода), и только затем выбирает наиболее конкретный, не глядя на бокс / распаковку, что неоднозначно здесь.

4 голосов
/ 01 февраля 2009

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

На этой странице объясняются правила автобоксирования и выбора метода для вызова. Сначала компилятор пытается выбрать метод без использования какого-либо автобокса , потому что бокс и распаковка влекут за собой снижение производительности. Если ни один метод не может быть выбран без использования бокса, как в этом случае, тогда бокс находится в таблице для всех аргументов этого метода.

3 голосов
/ 01 февраля 2009

Когда вы говорите f (a, b ), компилятор не понимает, к какой функции он должен обращаться.

Это потому, что a является int , но аргумент, ожидаемый в f , является объектом. Поэтому компилятор решает преобразовать a в объект. Теперь проблема в том, что, если a может быть преобразован в объект, то может быть b .

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

Когда вы преобразуете a в Object вручную, компилятор просто ищет наиболее близкое совпадение и затем обращается к нему.

Почему компилятор не выбрал функция, которая может быть достигнута "делать минимально возможное количество конверсии в бокс / распаковку "?

См. Следующий случай:

f(boolean a, Object b)
f(Object a , boolean b)

Если мы вызываем как f (логическое a, логическое b) , какую функцию он должен выбрать? Это неоднозначно, верно? Точно так же это станет более сложным, когда будет много аргументов. Поэтому компилятор решил выдать вам предупреждение.

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

2 голосов
/ 01 февраля 2009

См. http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#20448

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

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

2 голосов
/ 01 февраля 2009

Так почему же разрешение перегрузки потерпеть поражение? Почему не автобокс компилятора первый аргумент, и принять второй аргумент нормально? Почему я нужно запросить автобокс явно

Второй аргумент обычно не принимается. Помните, что «логическое» также может быть помещено в объект. Вы могли бы явно привести логический аргумент к Object, и это сработало бы.

1 голос
/ 01 февраля 2012

Компилятор Java разрешает перегруженные методы и конструкторы поэтапно. На первом этапе [§15.12.2.2] он определяет применимые методы путем подтипа [§4.10]. В этом примере ни один из методов не применим, потому что int не является подтипом Object.

На втором этапе [§15.12.2.3] компилятор идентифицирует применимые методы путем преобразования вызова метода [§5.3], которое является комбинацией автобокса и подтипа. Аргумент int может быть преобразован в Integer, который является подтипом Object, для обеих перегрузок. Логический аргумент не нуждается в преобразовании для первой перегрузки и может быть преобразован в логический, подтип Object, для второго. Поэтому оба метода применимы на втором этапе.

Поскольку применимо более одного метода, компилятор должен определить, какой из них наиболее специфичен [§15.12.2.5]. Он сравнивает типы параметров, а не типы аргументов, и не устанавливает их автоматически. Object и boolean - это не связанные типы, поэтому они считаются одинаково специфичными. Ни один метод не является более конкретным, чем другой, поэтому вызов метода неоднозначен.

Одним из способов устранения неоднозначности было бы изменение логического параметра на тип Boolean, который является подтипом Object. Первая перегрузка всегда будет более конкретной (если применимо), чем вторая.

...