Подтвердить что ты не робот

Java 8 лямбда двусмысленный метод для функционального интерфейса - Тип цели

У меня есть код ниже:

public class LambdaTest1 {

    public static void method1(Predicate<Integer> predicate){
        System.out.println("Inside Predicate");
    }

    public static void method1(Function<Integer,String> function){
        System.out.println("Inside Function");
    }

    public static void main(String[] args) {        
        method1((i) -> "Test"); 
    }
}

Это дает мне сообщение об ошибке как

"Метод method1 (Predicate) неоднозначен для типа LambdaTest1".

Я вижу, что для функционального интерфейса Function и Consumer входной аргумент Integer. Но для Function возвращаемый тип String. Поскольку мой вызов имеет тип возврата как "Текст". Это должно было называться моим функциональным интерфейсом Function вместо того, чтобы бросать эту ошибку.

Может кто-нибудь объяснить, почему это поведение?

Еще один пример:

public class LambdaTest1 {

public static void method1(Consumer<Integer> consumer){
    System.out.println("Inside Consumer");
}

public static void method1(Predicate<Integer> predicate){
    System.out.println("Inside Predicate");
}

public static void main(String[] args) {

    List<Integer> lst = new ArrayList<Integer>();

    method1(i -> true); 

    method1(s -> lst.add(s)); //ambiguous error
}
}

Также в приведенном выше коде используется метод линии1 (s → lst.add(s)); дает ошибку ambiguos, но где, как приведенный выше метод линии1 (i → true) работает нормально.

4b9b3361

Ответ 1

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

В частности, в первом примере выражение лямбда (i) -> "Test" представляет собой неявно типизированное лямбда-выражение, возвращающие значения которого не учитываются при разрешении перегрузки, а при изменении его, например, (Integer i) -> "Test" превратит его в явно введенное лямбда-выражение, возвращающие значения. Сравните с Спецификация языка Java §15.12.2.2.:

Этап 1: Определение методов сопоставления Arity, применимых по строгим вызовам

Выражение аргумента считается применимым к применимому для потенциально применимого метода m, если оно не имеет одну из следующих форм:

  • Неявно типизированное лямбда-выражение (§15.27.1).

...

  • Явно выраженное лямбда-выражение, тело которого является выражением, которое не имеет отношения к применимости.

  • Явно выраженное лямбда-выражение, тело которого является блоком, где хотя бы одно выражение результата не имеет отношения к применимости.

...

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

Значение неявно типизированного лямбда-выражения или неточного описания ссылки метода достаточно неопределенно до разрешения целевого типа, что аргументы, содержащие эти выражения, не считаются применимыми применимостью; они просто игнорируются (за исключением их ожидаемой ясности), пока не будет достигнуто разрешение перегрузки.

Таким образом, использование неявно введенного (i) -> "Test" не помогает решить, следует ли вызывать method1(Predicate<Integer>) или method1(Function<Integer,String>), и поскольку ни один из них не является более конкретным, выбор метода не выполняется, прежде чем пытаться вывести тип функции лямбда-выражений.

В другом случае выбор между method1(Consumer<Integer>) и method1(Predicate<Integer>) отличается, так как один метод имеет тип функции с возвратом void, а остальные - тип возврата void, который позволяет выбрать применимый метод через форму лямбда-выражения, о котором уже говорилось в связанном ответе. i -> true является только совместимым значением, поэтому не подходит для Consumer. Аналогично, i -> {} совместим только с void, поэтому не подходит для Predicate.

Есть только несколько случаев, когда форма неоднозначна:

  • когда блок никогда не завершается нормально, например. arg -> { throw new Exception(); } или
    arg -> { for(;;); }
  • когда выражение лямбда имеет вид arg -> expression и expression также является выражением. Такие выражения выражают
    • Назначения, например. arg -> foo=arg
    • Приращения/декрементные выражения, например. arg -> counter++
    • Вызов метода, как в вашем примере s -> lst.add(s)
    • Мгновенные действия, например. arg -> new Foo(arg)

Обратите внимание, что заключенные в скобки выражения не входят в этот список, поэтому изменение s -> lst.add(s) на
s -> (lst.add(s)) достаточно, чтобы превратить его в выражение, которое больше не совместимо с void. Аналогично, превращение его в утверждение, подобное s -> {lst.add(s);}, перестает быть совместимым с ценностью. Поэтому его легко выбрать правильный метод в этом сценарии.