Ответ 1
Эта строка однозначно неоднозначна:
doStuff(getPattern(x -> String.valueOf(x)));
Перечитайте это из связанной главы JLS:
Лямбда-выражение (§15.27) потенциально совместимо с функциональным интерфейсом типа (§9.8), если все следующие верно:
Арность типа типа целевого типа такая же, как и арность выражения лямбда.
Если тип функции целевого типа имеет возврат void, то тело лямбда является выражением оператора (§14.8) или блоком, совместимым с void (§15.27.2).
- Если тип функции целевого типа имеет (не-void) тип возврата, то тело лямбда является либо выражением, либо совместимым с значением блоком (§15.27.2).
В вашем случае для Consumer
у вас есть выражение , поскольку любой вызов метода может использоваться как выражение выражения, даже если метод не является void. Например, вы можете просто написать это:
public void test(Object x) {
String.valueOf(x);
}
Это не имеет никакого смысла, но компилируется отлично. У вашего метода может быть побочный эффект, компилятор не знает об этом. Например, был ли он List.add
, который всегда возвращает true
, и никто не заботится о его возвращаемом значении.
Конечно, эта лямбда также квалифицируется как Function
как выражение. Таким образом, это двусмысленность. Если у вас есть что-то, что является выражением, но не выражением оператора, тогда вызов будет отображаться на Function
без каких-либо проблем:
doStuff(getPattern(x -> x == null ? "" : String.valueOf(x)));
Когда вы меняете его на { return String.valueOf(x); }
, вы создаете совместимый с ценностью блок, поэтому он соответствует Function
, но он не квалифицируется как пустота - совместимый блок. Однако у вас могут быть проблемы с блоками:
doStuff(getPattern(x -> {throw new UnsupportedOperationException();}));
Этот блок квалифицируется как совместимый по стоимости и совместимый с void, так что у вас снова есть неоднозначность. Еще один пример блокировки из-за границы - бесконечный цикл:
doStuff(getPattern(x -> {while(true) System.out.println(x);}));
Что касается System.out.println(x)
, то это немного сложно. Он, безусловно, квалифицируется как выражение выражения, поэтому его можно сопоставить с Consumer
, но похоже, что он соответствует выражению, а spec говорит, что выражение method - это выражение. Однако в выражении ограниченного использования вроде 15.12.3 говорится:
Если объявление времени компиляции является недействительным, то вызов метода должен быть выражением верхнего уровня (то есть выражением в выражении или в компоненте ForInit или ForUpdate инструкции for) или во время компиляции возникает ошибка. Такой вызов метода не дает значения и поэтому должен использоваться только в ситуации, когда значение не требуется.
Таким образом, компилятор отлично следит за спецификацией. Сначала он определяет, что ваше тело лямбда квалифицируется как выражение (даже если его тип возврата недействителен: 15.12.2.1 не делает исключения для этого случая) и выражение выражения, поэтому оно также считало двусмысленность.
Таким образом, для меня оба утверждения компилируются в соответствии со спецификацией. ECJ-компилятор создает те же сообщения об ошибках в этом коде.
В общем, я бы посоветовал вам не перегружать свои методы, когда ваши перегрузки имеют одинаковое количество параметров и имеют разницу только в принятом функциональном интерфейсе. Даже если эти функциональные интерфейсы имеют различную арность (например, Consumer
и BiConsumer
): у вас не будет проблем с лямбдой, но могут иметь проблемы с ссылками на методы. Просто выберите разные имена для ваших методов в этом случае (например, processStuff
и consumeStuff
).