Почему потребители принимают лямбды с телами утверждений, но не с телами выражений?


следующий код удивительно успешно компилируется:

Consumer<String> p = ""::equals;

такое:

p = s -> "".equals(s);

но это не удается с ошибкой boolean cannot be converted to void как и ожидалось:

p = s -> true;

модификация второго примера со скобками также не удается:

p = s -> ("".equals(s));

это ошибка в компиляторе Java или есть правило вывода типа, о котором я не знаю?

3 56

3 ответа:

во-первых, стоит посмотреть на то, что Consumer<String> на самом деле. из документации:

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

таким образом, это функция, которая принимает строку и ничего не возвращает.

Consumer<String> p = ""::equals;

успешно компилируется, потому что equals можно взять строку (и, действительно, любой объект). Результат equals просто игнорируется.*

p = s -> "".equals(s);

это точно то же самое, но с другим синтаксисом. Компилятор знает, что не следует добавлять неявное return потому что a Consumer не должно возвращать значение. Это б добавить неявное 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() возвращает a boolean значением, но что вы не храните результат этого.

  • когда вы пишите s -> true тело лямбда : trueэто одно выражение .
    Компилятор считает, что функция возвращает обязательноboolean.
    Так что только дескриптор String->boolean можно использовать.

теперь вернемся к вашему коду, который не компилируется.
Что ты пытаешься сделать ?

Consumer<String> p = s -> true;

вы не можете. Вы хотите назначить переменной, которая использует дескриптор функции Consumer<String> лямбда тело с String->void дескриптор функции. Это не соответствует !