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

StackOverflow https://stackoverflow.com/questions/501412

Вопрос

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

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

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

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

Это было полезно?

Решение

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

Другие советы

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

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

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

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

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

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

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

Смотрите следующий пример:

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

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

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

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

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

Видишь http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#20448

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top