Почему эта Java-лямбда не удается скомпилировать?
Следующий код Java не скомпилируется:
@FunctionalInterface
private interface BiConsumer<A, B> {
void accept(A a, B b);
}
private static void takeBiConsumer(BiConsumer<String, String> bc) { }
public static void main(String[] args) {
takeBiConsumer((String s1, String s2) -> new String("hi")); // OK
takeBiConsumer((String s1, String s2) -> "hi"); // Error
}
Отчеты компилятора:
Error:(31, 58) java: incompatible types: bad return type in lambda expression
java.lang.String cannot be converted to void
Странно, что строка с надписью "OK" компилируется отлично, но строка с пометкой "Ошибка" терпит неудачу. Они кажутся практически идентичными.
Ответы
Ответ 1
Ваша лямбда должна быть конгруэнтной с BiConsumer<String, String>
. Если вы ссылаетесь на JLS # 15.27.3 (Тип лямбда):
Лямбда-выражение конгруэнтно с функциональным типом, если все верно:
- [...]
- Если результат типа функции недействителен, тело лямбда является выражением оператора (§14.8) или блоком, совместимым с void.
Итак, лямбда должна быть выражением оператора или блоком, совместимым с void:
- Вызов конструктора выражение выражения, поэтому он компилируется.
- Строковый литерал не является выражением оператора и не совместим с void (см. примеры из 15.27.2), поэтому он не компилируется.
Ответ 2
В основном, new String("hi")
является исполняемым фрагментом кода, который на самом деле что-то делает (он создает новую строку и затем возвращает ее). Возвращаемое значение можно игнорировать, а new String("hi")
все еще можно использовать в lambda void-return для создания новой строки.
Однако "hi"
- это просто константа, которая ничего не делает по своему усмотрению. Единственная разумная вещь, связанная с этим в лямбда-теле, - это вернуть ее. Но метод лямбда должен был бы иметь тип возврата String
или Object
, но он возвращает void
, следовательно, ошибку String cannot be casted to void
.
Ответ 3
Первый случай - это нормально, потому что вы вызываете "специальный" метод (конструктор), и вы фактически не принимаете созданный объект. Чтобы сделать это более ясным, я поставлю необязательные фигурные скобки в ваших лямбдах:
takeBiConsumer((String s1, String s2) -> {new String("hi");}); // OK
takeBiConsumer((String s1, String s2) -> {"hi"}); // Error
И более понятно, я переведу это в более старую нотацию:
takeBiConsumer(new BiConsumer<String, String>(String s1, String s2) {
public void accept(String s, String s2) {
new String("hi"); // OK
}
});
takeBiConsumer(new BiConsumer<String, String>(String s1, String s2) {
public void accept(String s, String s2) {
"hi"; // Here, the compiler will attempt to add a "return"
// keyword before the "hi", but then it will fail
// with "compiler error ... bla bla ...
// java.lang.String cannot be converted to void"
}
});
В первом случае вы выполняете конструктор, но вы НЕ возвращаете созданный объект, во втором случае вы пытаетесь вернуть значение String, но ваш метод в вашем интерфейсе BiConsumer
возвращает void, поэтому компилятор ошибка.
Ответ 4
JLS указывает, что
Если результат типа функции недействителен, тело лямбда является либо выражение выражения (§14.8) или блок, совместимый с void.
Теперь посмотрим, что подробно,
Поскольку ваш метод takeBiConsumer
имеет тип void, принимающий лямбда new String("hi")
будет интерпретировать его как блок, например
{
new String("hi");
}
который действителен в пустоте, поэтому первый случай компилируется.
Однако в случае, когда лямбда -> "hi"
, такой блок, как
{
"hi";
}
недопустимый синтаксис в java. Поэтому единственное, что нужно сделать с "привет", - это попытаться вернуть его.
{
return "hi";
}
который недействителен в пустоте и объясняет сообщение об ошибке
incompatible types: bad return type in lambda expression
java.lang.String cannot be converted to void
Для лучшего понимания обратите внимание, что если вы измените тип takeBiConsumer
на String, будет действителен -> "hi"
, поскольку он просто попытается напрямую вернуть строку.
Обратите внимание, что сначала я ошибся, что ошибка была вызвана тем, что lambda находится в неправильном контексте вызова, поэтому я поделился этой возможностью с сообществом:
JLS 15.27
Это ошибка времени компиляции, если выражение lambda возникает в программе в другом месте, кроме контекста назначения (§5.2), вызов контекст (§5.3) или контекст кастинга (§5.5).
Однако в нашем случае мы находимся в контексте вызова, который является правильным.