Возьмем функциональный интерфейс Functional (для краткости реализацию я опустил и упростил корпус):
@FunctionalInterface
public interface Functional<E> {
void perform(E e);
default <T extends Number> void method(E e, T t) { }
default <T extends Number> void method(E e, Function<E, T> function) { }
}
И простой фрагмент кода:
Functional<String> functional = (string) -> {};
functional.method("string", (string) -> 1);
Почему метод method() неоднозначен, поскольку в качестве параметра передается лямбда? Это должно быть легко отличить.
Затмение:
The method
method(String, Function<String,Integer>)is ambiguous for the typeFunctional<String>
Это также воспроизводится на IntelliJIdea.
Вывод Javac (спасибо @AndyTurner):
Main.java:21: error: reference to method is ambiguous
functional.method("string", (string) -> 1);
^
both method <T#1>method(E,T#1) in Functional and method <T#2>method(E,Function<E,T#2>) in Functional match
where T#1,E,T#2 are type-variables:
T#1 extends Number declared in method <T#1>method(E,T#1)
E extends Object declared in interface Functional
T#2 extends Number declared in method <T#2>method(E,Function<E,T#2>)
и
Main.java:21: error: incompatible types: cannot infer type-variable(s) T
functional.method("string", (string) -> 1);
^
(argument mismatch; Number is not a functional interface)
where T,E are type-variables:
T extends Number declared in method <T>method(E,T)
E extends Object declared in interface Functional
Редактировать: Интересный факт. Когда я заменяю default <T extends Number> на <T>, он работает. Кажется, что T не может расширять Number, Throwable и т. д.
default <T> void method(E e, T t) { }
default <T> void method(E e, Function<E, T> function) { }
Редактировать 2: Когда я добавляю универсальный тип T к объявлению интерфейса, он также работает:
@FunctionalInterface
public interface Functional<E, T extends Number> {
void get(E e);
default void method(E e, Function<E, T> function) { }
default void method(E e, T t) { }
}
@shmosel Function строго уже, чем Object, но Number и Function «связаны». (И там мог будет классом, который расширяет Number и реализует Function.)
@LouisWasserman Такой класс никогда не может быть реализован / создан как лямбда.
Это также работает, когда вы ссылаетесь на лямбду из переменной Function<String, ? extends Number>. Кажется странным, что он не может правильно определить, какой метод использовать при встраивании.
Скажите, какие именно версии jdk вы используете
Я тестировал это с помощью java 11, и он тоже не компилируется. Мне это кажется ошибкой. Как (string) -> 1 мог быть когда-либо Number?
Интересно, что предоставление явного параметра типа устраняет двусмысленность, т.е. functional.<Number>method("string", (string) -> 1); работает нормально.
есть причина, по которой некоторые языки не позволяют использовать методы перегрузки: каждый раз, когда вы смешиваете перегрузку с поли-выражениями, такими как лямбда-выражения, с точки зрения разработчика все становится «простым» - для компилятора все намного сложнее.




Есть несколько билетов (здесь, здесь и здесь), которые содержат похожие фрагменты кода. Эти билеты помечаются как «Не проблема» и объясняются следующим образом:
An expression is potentially compatible with a target type according to the following rules:
- [...]
- A lambda expression or a method reference expression is potentially compatible with a type variable if the type variable is a type parameter of the candidate method.
Итак, оба метода method в этом случае потенциально совместимы.
Кроме того, лямбда (string) -> 1 не имеет отношения к применимости, потому что:
An argument expression is considered pertinent to applicability for a potentially applicable method
munless it has one of the following forms
- [...]
- If
mis a generic method and the method invocation does not provide explicit type arguments, an explicitly typed lambda expression or an exact method reference expression for which the corresponding target type (as derived from the signature of m) is a type parameter ofm.
Ну наконец то:
Since the
methodhas a type-parameter where the lambda is passed as argument, the lambda is skipped from the applicability check - meaning both are applicable - hence the ambiguity.
Возможный обходной путь - приведите аргумент при вызове метода:
functional.method("string", (Function<String, Number>) (string) -> 1);
Я ожидал, что
<T extends Number>сработает, а<T>не сработает, потому что он будет совместим с любым аргументом. Интересно, что все обстоит наоборот. Для записи, не имеет значения, есть ли у второго методаextends Number.