Почему эта лямбда Java 8 не компилируется?
не удается скомпилировать следующий код 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
странно то, что строка с пометкой "ОК" компилируется нормально, но строка с пометкой "ошибка" терпит неудачу. Они кажутся практически идентичными.
4 ответа:
ваша лямбда должна быть конгруэнтна с
BiConsumer<String, String>
. Если вы ссылаетесь на JLS #15.27.3 (тип лямбды):лямбда-выражение совпадает с типом функции, если выполняются все следующие условия:
- [...]
- если результат типа функции равен void, то лямбда-тело является либо выражением оператора (§14.8), либо блоком, совместимым с void.
поэтому лямбда должна быть либо a выражение оператора или совместимый с void блок:
- вызов конструктора выражение оператора Так что он компилирует.
- строковый литерал не является выражением оператора и не совместим с void (см. примеры в 15.27.2), поэтому он не компилируется.
по сути,
,new String("hi")
- это исполняемый фрагмент кода, который на самом деле что-то делает (он создает новую строку, а затем возвращает ее). Возвращаемое значение можно игнорировать иnew String("hi")
все еще можно использовать в void-return lambda для создания новой строки."hi"
это просто константа, которая ничего не делает сама по себе. Единственное разумное, что можно сделать с ним в лямбда-теле-это возвращение его. Но лямбда-метод должен иметь возвращаемый типString
илиObject
, но он возвращаетvoid
, отсюдаString cannot be casted to void
ошибка.
первый случай в порядке, потому что вы вызываете "специальный" метод (конструктор), и вы фактически не принимаете созданный объект. Просто чтобы было понятнее, я поставлю дополнительные скобки в ваши лямбды:
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" } });
в первом случае вы выполняете конструктор, но не возвращаете созданный объект, во втором случае вы пытаетесь вернуть строковое значение, но ваш метод в вашем интерфейсе
BiConsumer
возвращает void, отсюда и ошибка компилятора.
JLS указывает, что
если результат типа функции является пустым, лямбда-тело является либо a выражение оператора (§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
в строку-> "hi"
будет действительным, так как он просто попытается напрямую вернуть строку.
обратите внимание, что сначала я ошибка была вызвана тем, что лямбда находится в неправильном контексте вызова, поэтому я поделюсь этой возможностью с сообществом:
это-ошибка времени компиляции, если лямбда-выражение происходит в программе в каком-либо месте, отличном от контекста присваивания (§5.2), вызов контекст (§5.3), или контекст приведения (§5.5).
однако в нашем случае, мы находимся в ссылка контекст что верно.