Почему метод 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.