87

The following Java code fails to compile:

@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
}

The compiler reports:

Error:(31, 58) java: incompatible types: bad return type in lambda expression
    java.lang.String cannot be converted to void

The weird thing is that the line marked "OK" compiles fine, but the line marked "Error" fails. They seem essentially identical.

imz -- Ivan Zakharyaschev
  • 4,921
  • 6
  • 53
  • 104
Rag
  • 6,405
  • 2
  • 31
  • 38
  • 5
    is it a typo here that the functional interface method returns void? – Nathan Hughes Mar 25 '15 at 17:13
  • 6
    @NathanHughes Nope. It turns out to be central to the question- see the accepted answer. – Rag Mar 26 '15 at 06:23
  • should there be code inside the `{ }` of `takeBiConsumer` ... and if so, could you give an example ... if I read this correctly, `bc` is an instance of the class/interface `BiConsumer`, and thus should contain a method called `accept` to match the interface signature ... ... and if that is right, then the `accept` method needs to be defined somewhere (eg a class that implements the interface) ... so is that what should be in the `{}`?? ... ... ... thanks – dsdsdsdsd Apr 29 '16 at 14:27
  • Interfaces with a single method are interchangeable with lambdas in Java 8. In this case, `(String s1, String s2) -> "hi"` is an instance of BiConsumer. – Rag Apr 29 '16 at 21:38

4 Answers4

101

Your lambda needs to be congruent with BiConsumer<String, String>. If you refer to JLS #15.27.3 (Type of a Lambda):

A lambda expression is congruent with a function type if all of the following are true:

  • [...]
  • If the function type's result is void, the lambda body is either a statement expression (§14.8) or a void-compatible block.

So the lambda must either be a statement expression or a void compatible block:

Community
  • 1
  • 1
assylias
  • 321,522
  • 82
  • 660
  • 783
  • 31
    @BrianGordon A String literal is an expression (a constant expression to be precise) but not a statement expression. – assylias Mar 25 '15 at 18:24
44

Basicly, new String("hi") is an executable piece of code that actually does something (it creates a new String and then returns it). The returned value can be ignored and new String("hi") can still be used in void-return lambda to create a new String.

However, "hi" is just a constant that doesn't do anything on it's own. The only reasonable thing to do with it in lambda body is to return it. But the lambda method would have to have return type String or Object, but it returns void, hence the String cannot be casted to void error.

kajacx
  • 12,361
  • 5
  • 43
  • 70
  • 6
    The correct formal term is [*Expression Statement*](http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.8), an instance creation expression may appear at both places, where an expression or where a statement is required, while a `String` literal is just an *expression* which can’t be used in a *statement* context. – Holger Mar 25 '15 at 18:06
  • 2
    The accepted answer may be formally correct, but this one is a better explanation – edc65 Mar 26 '15 at 09:07
  • 3
    @edc65: that’s why this answer got upvoted as well. The reasoning for the rules and the non-formal intuitive explanation may indeed help, however, every programmer should be aware that there are formal rules behind it and in the case that the formal rule’s outcome is *not* intuitively understandable, the formal rule will still wins. E.g. `()->x++` is legal, while `()->(x++)`, basically doing exactly the same, is not… – Holger Mar 26 '15 at 09:56
23

The first case is ok because you are invoking a "special" method (a constructor) and you are no actually taking the created object. Just to make it more clear, I'll put the optional braces in your lambdas:

takeBiConsumer((String s1, String s2) -> {new String("hi");}); // OK
takeBiConsumer((String s1, String s2) -> {"hi"}); // Error

And more clear, I'll translate that to the older notation:

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"
    }
});

In the first case you are executing a constructor, but you are NOT returning the created object, in the second case you are attempting to return a String value, but your method in your interface BiConsumer returns void, hence the compiler error.

morgano
  • 17,210
  • 10
  • 45
  • 56
13

The JLS specify that

If the function type's result is void, the lambda body is either a statement expression (§14.8) or a void-compatible block.

Now let's see that in detail,

Since your takeBiConsumer method is of void type, the lambda receiving new String("hi") will interpret it as a block like

{
    new String("hi");
}

which is valid in a void, hence the first case compile.

However, in the case where the lambda is -> "hi", a block such as

{
    "hi";
}

is not valid syntax in java. Therefore the only thing to do with "hi" is to try and return it.

{
    return "hi";
}

which is not valid in a void and explain the error message

incompatible types: bad return type in lambda expression
    java.lang.String cannot be converted to void

For a better understanding, note that if you change the type of takeBiConsumer to a String, -> "hi" will be valid as it will simply try to directly return the string.


Note that at first I tought the error was caused by the lambda being in a wrong invocation context, so I'll share this possibility with the community :

JLS 15.27

It is a compile-time error if a lambda expression occurs in a program in someplace other than an assignment context (§5.2), an invocation context (§5.3), or a casting context (§5.5).

However in our case, we are in an invocation context which is correct.

Jean-François Savard
  • 20,626
  • 7
  • 49
  • 76