Чому цю лямбду Java 8 не вдається скомпілювати?


85

Наступний код 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

Дивна річ у тому, що рядок із позначкою "ОК" компілюється нормально, але рядок із позначкою "Помилка" не працює. Вони здаються по суті однаковими.


5
тут помилка, що метод функціонального інтерфейсу повертає void?
Натан Хьюз,

6
@NathanHughes Ні. Це виявляється центральним у питанні - див. Прийняту відповідь.
Брайан Гордон,

чи повинен бути код всередині { }of takeBiConsumer... і якщо так, чи можете ви навести приклад ... якщо я прочитав це правильно, bcце екземпляр класу / інтерфейсу BiConsumer, і, отже, повинен містити метод, викликаний acceptдля відповідності підпису інтерфейсу. .. ... і якщо це правильно, тоді acceptметод потрібно десь визначити (наприклад, клас, який реалізує інтерфейс) ... так це те, що повинно бути в {}?? ... ... ... спасибі
dsdsdsdsd

Інтерфейси з одним методом взаємозамінні з лямбдами в Java 8. У цьому випадку (String s1, String s2) -> "hi"є екземпляром BiConsumer <String, String>.
Брайан Гордон,

Відповіді:


100

Ваша лямбда повинна відповідати BiConsumer<String, String>. Якщо ви посилаєтесь на JLS # 15.27.3 (Тип лямбди) :

Лямбда-вираз збігається з типом функції, якщо все наступне відповідає дійсності:

  • [...]
  • Якщо результат типу функції порожній, тіло лямбда є або виразом оператора (§14.8), або блоком, сумісним з порожнечею.

Отже, лямбда повинна бути або виразом оператора, або блоком, сумісним з void:


31
@BrianGordon Рядковий літерал - це вираз (якщо бути точним постійним виразом), але не вираз оператора.
assylias

44

По суті, new String("hi")це виконуваний фрагмент коду, який насправді щось робить (створює новий рядок, а потім повертає його). Повернене значення можна ігнорувати і new String("hi")все ще використовувати у лямбда-функції void-return для створення нового рядка.

Однак "hi"це просто константа, яка нічого не робить сама по собі. Єдине розумне, що можна зробити з ним у лямбда-тілі - це повернути його. Але лямбда-метод повинен мати тип return Stringабо Object, але він повертається void, звідси String cannot be casted to voidпомилка.


6
Правильним формальним терміном є Expression Statement , вираз створення екземпляра може з'являтися в обох місцях, де потрібен вираз або де потрібно вираз, тоді як Stringлітерал - це просто вираз, який не можна використовувати в контексті висловлювання .
Holger

2
Формально прийнята відповідь може бути правильною, але ця є кращим поясненням
edc65,

3
@ edc65: ось чому ця відповідь також проголосувала. Міркування щодо правил та неформальне інтуїтивне пояснення дійсно можуть допомогти, однак, кожен програміст повинен усвідомлювати, що за цим є формальні правила, і в тому випадку, якщо результат офіційного правила інтуїтивно не зрозумілий, формальне правило все одно виграє . Наприклад, ()->x++це законно, в той час як ()->(x++), в основному, робити те саме те саме, не є ...
Холгер

21

Перший випадок - це нормально, оскільки ви використовуєте "спеціальний" метод (конструктор), і фактично не берете створений об'єкт. Щоб зробити це чіткіше, я вкладу додаткові фігурні дужки у ваші лямбди:

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, отже, помилка компілятора.


12

JLS це вказує

Якщо результат типу функції порожній, тіло лямбда є або виразом оператора (§14.8), або блоком, сумісним з порожнечею.

Тепер давайте подивимось це докладно,

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


Зверніть увагу, що спочатку я думав, що помилка була спричинена тим, що лямбда знаходиться в неправильному контексті виклику, тому я поділюсь цією можливістю із спільнотою:

JLS 15.27

Помилка під час компіляції, якщо лямбда-вираз виникає в програмі десь, крім контексту призначення (§5.2), контексту виклику (§5.3) або контексту кастингу (§5.5).

Однак у нашому випадку ми перебуваємо у контексті виклику, який є правильним.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.