Почему autoboxing делает некоторые вызовы неоднозначными в Java?


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

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

6 ответов:

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

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

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

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

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

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

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. Как только в фазе 2 не происходит селективного автобоксинга или распаковки. Компилятор найдет все методы, которые доступны (оба метода в этих случаях) и применимы (опять же два метода), и только затем выберет наиболее конкретный, не глядя на бокс/распаковку, что здесь неоднозначно.

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

Эта страница объясняет правила автобоксинга и выбора метода для вызова. Компилятор сначала пытается выбрать метод без использования автобоксинга вообще, потому что бокс и распаковка несут штрафы за производительность. Если ни один метод нельзя выбрать, не прибегая к боксу, как в этом case, то бокс находится на столе для всех аргументов этого метода.

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

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

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

При преобразовании A в объект вручную компилятор просто ищет наиболее близкое соответствие и затем ссылается на него.

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

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

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

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

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

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

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

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

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

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

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

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

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

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