Почему эта лямбда 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 81

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" будет действительным, так как он просто попытается напрямую вернуть строку.


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

JLS 15.27

это-ошибка времени компиляции, если лямбда-выражение происходит в программе в каком-либо месте, отличном от контекста присваивания (§5.2), вызов контекст (§5.3), или контекст приведения (§5.5).

однако в нашем случае, мы находимся в ссылка контекст что верно.