Я отвечал на вопрос и столкнулся со сценарием, который я не могу объяснить. Рассмотрим этот код:
interface ConsumerOne<T> {
void accept(T a);
}
interface CustomIterable<T> extends Iterable<T> {
void forEach(ConsumerOne<? super T> c); //overload
}
class A {
private static CustomIterable<A> iterable;
private static List<A> aList;
public static void main(String[] args) {
iterable.forEach(a -> aList.add(a)); //ambiguous
iterable.forEach(aList::add); //ambiguous
iterable.forEach((A a) -> aList.add(a)); //OK
}
}
Я не понимаю, почему при явном вводе параметра лямбда (A a) -> aList.add(a)
-кода код компилируется. Кроме того, почему это связано с перегрузкой в, Iterable
а не в CustomIterable
?
Есть ли какое-то объяснение этому или ссылка на соответствующий раздел спецификации?
Примечание: iterable.forEach((A a) -> aList.add(a));
компилируется только при CustomIterable<T>
расширении Iterable<T>
(полная перегрузка методов CustomIterable
приводит к неоднозначной ошибке)
Получение этого на обоих:
- openjdk версия "13.0.2" 2020-01-14
Компилятор Eclipse - openjdk версия "1.8.0_232"
компилятор Eclipse
Изменить : Приведенный выше код не компилируется при сборке с Maven, в то время как Eclipse успешно компилирует последнюю строку кода.
Ответы:
TL; DR, это ошибка компилятора.
Не существует правила, которое бы отдавало приоритет конкретному применимому методу, когда он наследуется, или методу по умолчанию. Интересно, когда я меняю код на
iterable.forEach((A a) -> aList.add(a));
оператор выдает ошибку в Eclipse.Поскольку ни одно свойство
forEach(Consumer<? super T) c)
метода изIterable<T>
интерфейса не изменилось при объявлении другой перегрузки, решение Eclipse выбрать этот метод не может (последовательно) основываться на каком-либо свойстве метода. Это все еще единственный унаследованный метод, все еще единственныйdefault
метод, все еще единственный метод JDK и так далее. В любом случае ни одно из этих свойств не должно влиять на выбор метода.Обратите внимание, что изменение объявления на
также выдает «неоднозначную» ошибку, поэтому число применимых перегруженных методов также не имеет значения, даже если есть только два кандидата, нет общего предпочтения в отношении
default
методов.До сих пор проблема, кажется, появляется, когда есть два применимых метода и
default
метод и отношения наследования, но это не правильное место, чтобы копать дальше.Но понятно, что конструкции вашего примера могут обрабатываться другим кодом реализации в компиляторе, один демонстрирует ошибку, а другой нет.
a -> aList.add(a)
является неявно типизированным лямбда-выражением, которое нельзя использовать для разрешения перегрузки. Напротив,(A a) -> aList.add(a)
это явно типизированное лямбда-выражение, которое можно использовать для выбора подходящего метода из перегруженных методов, но здесь это не помогает (здесь не должно помогать), поскольку все методы имеют типы параметров с одинаковой функциональной сигнатурой. ,В качестве контр-примера, с
функциональные сигнатуры различаются, и использование лямбда-выражения явно типа может действительно помочь в выборе правильного метода, тогда как неявно типизированное лямбда-выражение не помогает, поэтому
forEach(s -> s.isEmpty())
выдает ошибку компилятора. И все компиляторы Java согласны с этим.Обратите внимание, что
aList::add
это неоднозначная ссылка наadd
метод, так как метод также перегружен, поэтому он также не может помочь в выборе метода, но ссылки на методы в любом случае могут обрабатываться другим кодом. Переключение на однозначноеaList::contains
или измененияList
вCollection
, чтобы сделатьadd
однозначный, не изменить результат в моей установке Eclipse , (я2019-06
).источник
default
методом, является лишь дополнительным моментом. Мой ответ уже показывает пример, в котором Eclipse не отдает приоритет методу по умолчанию.default
меняет результат и сразу предполагаете, что нашли причину наблюдаемого поведения. Вы настолько самоуверенны в этом, что называете другие ответы неправильными, несмотря на то, что они даже не противоречат. Поскольку вы проецируете свое собственное поведение на других, я никогда не утверждал, что причиной было наследство . Я доказал, что это не так. Я продемонстрировал, что поведение противоречиво, поскольку Eclipse выбирает конкретный метод в одном сценарии, но не в другом, где существуют три перегрузки.default
другойabstract
, с двумя аргументами, подобными потребителю, и пробую его. Затмение правильно говорит, что это неоднозначно, несмотря на то, что этоdefault
метод. Очевидно, наследование все еще имеет отношение к этой ошибке Eclipse, но в отличие от вас, я не схожу с ума и называю другие ответы неправильными, просто потому, что они не проанализировали ошибку полностью. Это не наша работа здесь.Компилятор Eclipse правильно разрешает
default
метод , поскольку это наиболее специфический метод в соответствии со спецификацией языка Java 15.12.2.5 :javac
(используется Maven и IntelliJ по умолчанию) говорит, что вызов метода здесь неоднозначен. Но в соответствии со спецификацией языка Java это не является двусмысленным, так как один из двух методов является наиболее конкретным здесь.Неявно типизированный лямбда-выражения обрабатываются иначе, чем явно типизированные лямбда-выражения в Java. Неявно типизированные, в отличие от явно типизированных лямбда-выражений, проходят первую фазу, чтобы идентифицировать методы строгого вызова (см. Спецификацию языка Java jls-15.12.2.2 , первая точка). Следовательно, вызов метода здесь неоднозначен для неявно типизированных лямбда-выражений.
В вашем случае, обходной путь для этой
javac
ошибки - указать тип функционального интерфейса вместо использования явно лямбда-выражения, как указано ниже:или
Вот ваш пример, дополнительно свернутый для тестирования:
источник
default
метод, когда есть три метода-кандидата или когда оба метода объявлены в одном и том же интерфейсе.forEach(Consumer)
иforEach(Consumer2)
которые никогда не могут закончиться в одном и том же методе реализации.Код, в котором Eclipse реализует JLS §15.12.2.5, не находит ни один метод более специфичным, чем другой, даже в случае явно лямбда с типизированным типом.
Так что в идеале «Затмение» остановится здесь и сообщит о двусмысленности. К сожалению, реализация разрешения перегрузки имеет нетривиальный код в дополнение к реализации JLS. Насколько я понимаю, этот код (который датируется временем, когда Java 5 была новой) должен быть сохранен, чтобы заполнить некоторые пробелы в JLS.
Я подал https://bugs.eclipse.org/562538, чтобы отследить это.
Независимо от этой конкретной ошибки, я могу только настоятельно рекомендовать против этого стиля кода. Перегрузка хороша для большого количества неожиданностей в Java, умноженная на вывод типа лямбда, сложность совершенно непропорциональна полученному усилению.
источник