Почему метод for-each в java не генерирует исключение при передаче аргумента типа Function вместо Consumer?

Почему не forEach метод в Java не показывает ошибку компилятора при передаче аргумента типа функции вместо потребителя? Здесь обе строки возвращают логическое значение для каждого элемента в потоке, но только 2-я строка получает ошибку компиляции? Есть ли другое свойство лямбда-выражения для такого сценария?

Вот мой код:

Stream.of(1,2,3,4).forEach(a->a.equals(1));//line 1

Stream.of(1,2,3,4).forEach(a->{return a.equals(1);});//line 2

3 ответа

В первой строке вы указали действительный Consumer который принимает аргумент int a, Это то, что Stream.forEach надеется. Тот факт, что потребитель дополнительно вернет стоимость, не имеет значения. Возвращенное значение не будет оцениваться, оно будет отброшено.

Вторая строка содержит инструкцию, которая возвращает boolean, Поскольку этот оператор имеет возвращаемое значение, он не соответствует методу Consumer который принимает аргумент, но объявлен void, Таким образом, это дает ошибку времени компиляции: методы Void не могут возвращать значение

JLS 15.27.2 говорит:

Тело лямбда-блока является void-совместимым, если каждый оператор return в блоке имеет форму return;.

Таким образом, метод возвращает без явного возвращаемого значения.

Относительно возврата заявления JLS 14.17. говорит:

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

  • Метод, который объявлен с использованием ключевого слова void, чтобы не возвращать значение (§8.4.5)

Применение этого ко второй строке приводит к следующему коду:

Stream.of(1, 2, 3, 4).forEach(a -> { a.equals(1); return; });

Дальнейшие заметки:
Возвращая значение equals во второй строке это утверждение не становится Function, Это все еще только заявление.

Исключение не будет выдано, поскольку исключение может быть выдано только во время выполнения. Поскольку код не компилируется, он не может быть выполнен. Как указано в JLS, выдается ошибка времени компиляции.

Относительно того, почему работает первая строка: в спецификации есть объяснение:

Вообще говоря, лямбда вида () -> expr, где expr является выражением оператора, интерпретируется как () -> { return expr; } или же () -> { expr; }в зависимости от типа цели.

Выше приведен следующий пример (хорошее совпадение, это очень похоже на ваш пример):

// Consumer has a void result
java.util.function.Consumer<String> c = s -> list.add(s);

Это просто означает, что компилятор игнорирует тип возвращаемого значения для выражения, как если бы ваш код был просто следующим (что действительно для метода void):

Stream.of(1, 2, 3, 4).forEach(a -> {
     a.equals(1);
});

Что касается второй строки, то в спецификации сказано:

Тело лямбда-блока является void-совместимым, если каждый оператор возврата в блоке имеет вид return;,

В вашем случае, однако, {return a.equals(1);} не соответствует этому правилу. Пустые методы не возвращают значение.

Простой способ понять это - учесть, что компилятор применяет правила проверки тела метода (чтобы тело было совместимо с объявлением). public void accept(T t)) - как уже упоминалось в учебнике

Фактически, в первой строке у вас есть Consumer, поскольку возвращаемое значение игнорируется. Но во второй строке вы возвращаете результат явно, так что выражение стало типом Function.

Другие вопросы по тегам