Метод неоднозначен при передаче лямбда-выражения в Java

Возьмем функциональный интерфейс 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 type Functional<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)  { }
} 

Я ожидал, что <T extends Number> сработает, а <T> не сработает, потому что он будет совместим с любым аргументом. Интересно, что все обстоит наоборот. Для записи, не имеет значения, есть ли у второго метода extends Number.

shmosel 06.12.2018 00:28

@shmosel Function строго уже, чем Object, но Number и Function «связаны». (И там мог будет классом, который расширяет Number и реализует Function.)

Louis Wasserman 06.12.2018 00:29

@LouisWasserman Такой класс никогда не может быть реализован / создан как лямбда.

shmosel 06.12.2018 00:38

Это также работает, когда вы ссылаетесь на лямбду из переменной Function<String, ? extends Number>. Кажется странным, что он не может правильно определить, какой метод использовать при встраивании.

Vince 06.12.2018 00:59

Скажите, какие именно версии jdk вы используете

fps 06.12.2018 02:13

Я тестировал это с помощью java 11, и он тоже не компилируется. Мне это кажется ошибкой. Как (string) -> 1 мог быть когда-либо Number?

fps 06.12.2018 02:19

Интересно, что предоставление явного параметра типа устраняет двусмысленность, т.е. functional.<Number>method("string", (string) -> 1); работает нормально.

fps 06.12.2018 02:33

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

Eugene 06.12.2018 10:47
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
12
8
820
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Есть несколько билетов (здесь, здесь и здесь), которые содержат похожие фрагменты кода. Эти билеты помечаются как «Не проблема» и объясняются следующим образом:

JLS 15.12.2.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 не имеет отношения к применимости, потому что:

JLS 15.2.2.2:

An argument expression is considered pertinent to applicability for a potentially applicable method m unless it has one of the following forms

  • [...]
  • If m is 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 of m.

Ну наконец то:

Since the method has 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);

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