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

3

5

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;
    }
}
Oleum answered 11/3, 2021 at 17:50 Comment(0)
C
8

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.

Celestina answered 11/3, 2021 at 18:33 Comment(2)
Just a question "is valid as the statement counter++; is void-compatible " and "In other words, counter++; and ++counter; are statement expressions," So counter++ is a void-compatible or a statement expression ?Trimming
@Trimming I'd put it as counter++; is a statement expression. Maybe my reference to void-compatible on that element is confusing because the JLS cares more about void-compatible blocks. Statement expressions are just okay to be used for void-returning functions. I'll edit.Celestina
T
2

/* 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

Trimming answered 11/3, 2021 at 18:15 Comment(0)
T
0

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
Tawanda answered 12/3, 2021 at 17:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.