Почему потребители принимают лямбды с телами утверждений, но не с телами выражений?
следующий код удивительно успешно компилируется:
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
дескриптор функции. Это не соответствует !