Почему автобоксирование делает некоторые вызовы неоднозначными на 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); }

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

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

Ответ 1

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

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

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

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

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

Ответ 2

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

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

Ответ 3

Когда вы говорите 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), какую функцию выбрать? Это однозначно? Точно так же это становится более сложным, когда присутствует множество аргументов. Поэтому компилятор решил дать вам предупреждение.

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

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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

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

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

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