Почему потребители принимают лямбды с телами утверждений, но не с телами выражений?
следующий код удивительно успешно компилируется:
Consumer<String> p = ""::equals;
такое:
p = s -> "".equals(s);
но это не удается с ошибкой boolean cannot be converted to void как и ожидалось:
p = s -> true;
модификация второго примера со скобками также не удается:
p = s -> ("".equals(s));
это ошибка в компиляторе Java или есть правило вывода типа, о котором я не знаю?
3 ответа:
во-первых, стоит посмотреть на то, что
Consumer<String>на самом деле. из документации:представляет собой операцию, которая принимает один входной параметр и не возвращает результата. В отличие от большинства других функциональных интерфейсов, потребитель ожидается, что он будет работать через побочные эффекты.
таким образом, это функция, которая принимает строку и ничего не возвращает.
Consumer<String> p = ""::equals;успешно компилируется, потому что
equalsможно взять строку (и, действительно, любой объект). Результат equals просто игнорируется.*p = s -> "".equals(s);это точно то же самое, но с другим синтаксисом. Компилятор знает, что не следует добавлять неявное
returnпотому что aConsumerне должно возвращать значение. Это б добавить неявноеreturnесли бы лямбда былаFunction<String, Boolean>хотя.p = s -> true;это принимает строку (
s), а потому чтоtrueэто выражение, а не оператор, результат не может быть игнорируется таким же образом. Компилятор до добавить неявноеreturnпотому что выражение не может существовать сама по себе. Таким образом, это тут возвращает: булево. Поэтому это неConsumer.**p = s -> ("".equals(s));опять же, это выражение, а не утверждение. Игнорируя лямбды на мгновение, вы увидите строку
System.out.println("Hello");точно так же не удастся скомпилировать, если вы обернете его в круглые скобки.
*от в спецификации:
если тело лямбды является выражением оператора (то есть выражением, которому было бы разрешено стоять отдельно в качестве оператора), оно совместимо с типом функции, создающим пустоту; любой результат просто отбрасывается.
лямбда-выражение совпадает с типом функции [void-producing] if ... лямбда тела является либо выражение оператора (§14.8) или блок, совместимый с пустотой.
Я думаю, что другие ответы усложняют объяснение, сосредотачиваясь на лямбдах, тогда как их поведение в этом случае похоже на поведение вручную реализованных методов. Это компилирует:
new Consumer<String>() { @Override public void accept(final String s) { "".equals(s); } }в то время как это не делает:
new Consumer<String>() { @Override public void accept(final String s) { true; } }, потому что
"".equals(s)это утверждение, ноtrueнет. Лямбда-выражение для функционального интерфейса, возвращающего void, требует оператора, поэтому оно следует тем же правилам, что и тело метода.обратите внимание, что в целом лямбда-тела не следуют ровно те же правила, что и тела методов - в частности, если лямбда, тело которой является выражением, реализует метод, возвращающий значение, он имеет неявное
return. Так, например,x -> trueбыло бы допустимой реализациейFunction<Object, Boolean>, аtrue;не является допустимым тело метода. Но в данном конкретном случае функциональные интерфейсы и тела методов совпадают.
s -> "".equals(s)и
s -> trueне полагайтесь на одни и те же дескрипторы функций.
s -> "".equals(s)может относиться либоString->voidилиString->booleanдескриптор функции.s -> trueотносится толькоString->booleanдескриптор функции.почему ?
- когда вы пишите
s -> "".equals(s)тело лямбда :"".equals(s)это оператор, который производит значение.
Компилятор считает, что функция может возвращать либоvoidилиboolean.
Итак, пишем :
Function<String, Boolean> function = s -> "".equals(s); Consumer<String> consumer = s -> "".equals(s);действителен.
когда вы назначаете лямбда-тело
Consumer<String>объявлена переменная, дескриптор есть.
Конечно, этот код не имеет большого смысла (вы проверяете равенство и не используете результат), но компилятору все равно.
Это то же самое, когда вы пишете заявление :myObject.getMyProperty()здесьgetMyProperty()возвращает abooleanзначением, но что вы не храните результат этого.
- когда вы пишите
s -> trueтело лямбда :trueэто одно выражение .
Компилятор считает, что функция возвращает обязательноboolean.
Так что только дескрипторString->booleanможно использовать.
теперь вернемся к вашему коду, который не компилируется.
Что ты пытаешься сделать ?Consumer<String> p = s -> true;вы не можете. Вы хотите назначить переменной, которая использует дескриптор функции
Consumer<String>лямбда тело сString->voidдескриптор функции. Это не соответствует !