Valid void Return Statements from Lambda Expressions (Example: Runnable)

Viewed 515

Seeing some stranger behavior in Java regarding functional interfaces with void return type.

Can someone please explain why the declarations for task5 and task6 below compile?

public class Test {

    private static int counter;

    private static void testRunnable() {
        /* Runnable has a void return type so the following won't compile.
        * Error message is Void methods cannot return a value. (Java 67108969) Makes sense... */
        // Runnable task1 = () -> { return counter; };
        // Runnable task2 = () -> { return counter + 1; };
        // Runnable task3 = () -> counter;
        // Runnable task4 = () -> counter + 1;

        /* But for some reason, this is totally acceptable. Why? */
        Runnable task5 = () -> counter++;
        Runnable task6 = () -> ++counter;
    }
}
3 Answers

The lambda expression () -> counter++; is valid as counter++; is a statement expression. This is explicitly permitted in the JLS:

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

And for the statement expression's definition:

An expression statement is executed by evaluating the expression; if the expression has a value, the value is discarded.

If you read the whole JLS 15.27.3, you'll see why () -> {return counter + 1;} is not void-compatible. A lambda body is not validated with the exact same rules as the simple expression statement.

In other words, counter++; and ++counter; are statement expressions, meaning that the expression can be evaluated and be treated as a statement whose result is simply discarded.

It's clearer (or rather more familiar-looking) when converted to a block body:

Runnable task5 = () -> {
    counter++; //result discarded, but you can't use "return counter++;"
};
Runnable task6 = () -> {
    ++counter; //result discarded, but you can't use "return ++counter;"
};

This has nothing to do with UnaryOperator, at least as far as that functional interface is concerned. It just happens that counter++ is compatible with IntUnaryOperator, but the expression could have been anything else, including (but not limited to) the following, and your question would still apply as the statement yields a result:

Runnable task5 = () -> counter += 1;
Runnable task6 = () -> counter = counter + 1;
Runnable task7 = () -> Math.addExact(counter, 1); // no change in counter
Runnable task8 = () -> return2(); //return2 takes no input

All these are int-producing expressions that are congruent with Runnable.run(). Specifically, task8 takes no input but produces a result; and it is not even compatible with any unary operator functional interface.

/* But for some reason, this is totally acceptable. Why? */ Runnable task5 = () -> counter++; Runnable task6 = () -> ++counter;

Because both tasks return void. counter++; and ++counter are operations who's results will be discarded. Moreover, none of them will be interpreted by the compiler as return counter++; or return ++counter;.

Task 1 and task 2 does not work for obvious reasons, both return and int:

 Runnable task1 = () -> { return counter; };
 Runnable task2 = () -> { return counter + 1;};

Task 3 and task 4 although do not explicit return and int, both do return implicitly an int:

Runnable task3 = () -> counter;
Runnable task4 = () -> counter + 1;

Task 1 and tasks are semantically the same as task 3 and task4 it is just syntax sugar.

In the task 1 and task 2, () -> { return counter; }; and () -> { return counter + 1;}; are statement lambda that can be replaced with the expression lambda () -> counter; and () -> counter + 1;, respectively

Example to understand the concept with these two functional interface:

Exp: 1. interface having method with void return type

interface InterfaceNameOne{
    void methodOne();  // method has void return type
}

InterfaceNameOne ref1 = () -> statement1;  // here should not be a return statement

Exp: 2. interface having method with any return type here int as example

interface InterfaceNameTwo{
    int methodTwo(int x);  // method has return type
}

InterfaceNameTwo ref2 = (x) -> {return statement;};  // here should be a return statement
InterfaceNameTwo ref3 = (x) -> statement;  // it is also a return statement

While Runnable is a functional interface like the first (1) example that's why there must not be a return statement like

Runnable ref4 = () -> counter=counter+1; // equivalent to counter++ & ++counter without return statement

But not like

Runnable ref5 = () -> counter; // equivalent to return statement
Runnable ref6 = () -> {return counter;}; // equivalent to return statement
Related