Ответ 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);}
, перестает быть совместимым с ценностью. Поэтому его легко выбрать правильный метод в этом сценарии.