Подтвердить что ты не робот

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

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

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

Это тоже:

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

Но это ошибка с ошибкой boolean cannot be converted to void, как ожидалось:

p = s -> true;

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

Это точно то же самое, но с другим синтаксисом. Компилятор не знает, чтобы добавить неявный return, потому что Consumer не должен возвращать значение. Он добавил бы неявный return, если лямбда была Function<String, Boolean>, хотя.

p = s -> true;

Это принимает строку (s), но поскольку true является выражением, а не выражением, результат нельзя игнорировать одинаково. Компилятор должен добавить неявный return, потому что выражение не может существовать самостоятельно. Таким образом, у этого есть возврат: булев. Поэтому это не a Consumer. **

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

Опять же, это выражение, а не утверждение. Игнорируя ламбы на мгновение, вы увидите, что строка System.out.println("Hello"); будет аналогичным образом не скомпилироваться, если вы завернете ее в круглые скобки.


* Из спецификация:

Если тело лямбда является выражением оператора (то есть выражением, которое можно было бы оставить в силе как оператор), оно совместимо с типом функции, производящей пустоты; любой результат просто отбрасывается.

** Из спецификация (спасибо, Eugene):

Лямбда-выражение конгруэнтно с функцией функции [void-production], если... тело лямбда представляет собой выражение выражения (§14.8) или совместимый с void блок.

Ответ 2

Я думаю, что другие ответы затрудняют объяснение, сосредотачиваясь на lambdas, тогда как их поведение в этом случае похоже на поведение методов, реализованных вручную. Это компилируется:

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; не является допустимым телом метода. Но в этом конкретном случае функциональные интерфейсы и тела методов совпадают.

Ответ 3

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

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

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

Consumer<String> p = s -> true;

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