Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do Consumers accept lambdas with statement bodies but not expression bodies?

The following code surprisingly is compiling successfully:

Consumer<String> p = ""::equals;

This too:

p = s -> "".equals(s);

But this is fails with the error boolean cannot be converted to void as expected:

p = s -> true;

Modification of the second example with parenthesis also fails:

p = s -> ("".equals(s));

Is it a bug in Java compiler or is there a type inference rule I don't know about?

like image 981
Zefick Avatar asked Aug 02 '17 12:08

Zefick


People also ask

Can lambda expression be used without functional interface?

Surely lambda expression can be one-time used as your commented code does, but when it comes to passing lambda expression as parameter to mimic function callback, functional interface is a must because in that case the variable data type is the functional interface.

What are the two conditions required for using a lambda function in a stream?

In order to match a lambda to a single method interface, also called a "functional interface", several conditions need to be met: The functional interface has to have exactly one unimplemented method, and that method (naturally) has to be abstract.

What is the use of lambda expression in Java?

It helps to iterate, filter and extract data from collection. The Lambda expression is used to provide the implementation of an interface which has functional interface. It saves a lot of code. In case of lambda expression, we don't need to define the method again for providing the implementation.

What is the return type of lambda expression?

The body of a lambda expression can contain zero, one, or more statements. When there is a single statement curly brackets are not mandatory and the return type of the anonymous function is the same as that of the body expression.


3 Answers

First, it's worth looking at what a Consumer<String> actually is. From the documentation:

Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, Consumer is expected to operate via side-effects.

So it's a function that accepts a String and returns nothing.

Consumer<String> p = ""::equals;

Compiles successfully because equals can take a String (and, indeed, any Object). The result of equals is just ignored.*

p = s -> "".equals(s);

This is exactly the same, but with different syntax. The compiler knows not to add an implicit return because a Consumer should not return a value. It would add an implicit return if the lambda was a Function<String, Boolean> though.

p = s -> true;

This takes a String (s) but because true is an expression and not a statement, the result cannot be ignored in the same way. The compiler has to add an implicit return because an expression can't exist on its own. Thus, this does have a return: a boolean. Therefore it's not a Consumer.**

p = s -> ("".equals(s));

Again, this is an expression, not a statement. Ignoring lambdas for a moment, you will see the line System.out.println("Hello"); will similarly fail to compile if you wrap it in parentheses.


*From the spec:

If the body of a lambda is a statement expression (that is, an expression that would be allowed to stand alone as a statement), it is compatible with a void-producing function type; any result is simply discarded.

**From the spec (thanks, Eugene):

A lambda expression is congruent with a [void-producing] function type if ... the lambda body is either a statement expression (§14.8) or a void-compatible block.

like image 143
Michael Avatar answered Oct 20 '22 07:10

Michael


I think the other answers complicate the explanation by focusing on lambdas whereas their behavior in this case is similar to the behavior of manually implemented methods. This compiles:

new Consumer<String>() {
    @Override
    public void accept(final String s) {
        "".equals(s);
    }
}

whereas this does not:

new Consumer<String>() {
    @Override
    public void accept(final String s) {
        true;
    }
}

because "".equals(s) is a statement but true is not. A lambda expression for a functional interface returning void requires a statement so it follows the same rules as a method's body.

Note that in general lambda bodies don't follow exactly the same rules as method bodies - in particular, if a lambda whose body is an expression implements a method returning a value, it has an implicit return. So for example, x -> true would be a valid implementation of Function<Object, Boolean>, whereas true; is not a valid method body. But in this particular case functional interfaces and method bodies coincide.

like image 12
Reinstate Monica Avatar answered Oct 20 '22 07:10

Reinstate Monica


s -> "".equals(s)

and

s -> true

don't rely on same function descriptors.

s -> "".equals(s) may refer either String->void or String->boolean function descriptor.
s -> true refers to only String->boolean function descriptor.

Why ?

  • when you write s -> "".equals(s), the body of the lambda : "".equals(s) is a statement that produces a value.
    The compiler considers that the function may return either void or boolean.

So writing :

Function<String, Boolean> function = s -> "".equals(s);
Consumer<String> consumer = s -> "".equals(s);

is valid.

When you assign the lambda body to a Consumer<String> declared variable, the descriptor String->void is used.
Of course, this code doesn't make much sense (you check the equality and you don't use the result) but the compiler doesn't care.
It is the same thing when you write a statement : myObject.getMyProperty() where getMyProperty() returns a boolean value but that you don't store the result of it.

  • when you write s -> true, the body of the lambda : true is a single expression .
    The compiler considers that the function returns necessarily boolean.
    So only the descriptor String->boolean may be used.

Now, come back to your code that doesn't compile.
What are you trying to do ?

Consumer<String> p = s -> true;

You cannot. You want to assign to a variable that uses the function descriptor Consumer<String> a lambda body with the String->void function descriptor. It doesn't match !

like image 8
davidxxx Avatar answered Oct 20 '22 08:10

davidxxx