I am studying for the OCP exam and I noticed a following snippet of code, where the parameter to the forEach method invoked on a DoubleStream must match that of the DoubleConsumer functional interface, however the lambda does not match the required types, why does it still compile?
DoubleStream.of(3.14159, 2.71828)
.forEach(c -> service.submit(
() -> System.out.println(10*c)
));
DoubleConsumer (accepts an argument of type Double and has a return type of void), however this forEach has a return type of Future<?>
where ? denotes the return type of the Runnable lambda which is void, Future - this is not void. I am saying this because the return type of service.submit(...) is Future<?>
it is not void, why does this code compile?
It is not really true that the return type of the lambda expression and the return type of the function type of the target functional interface type has to exactly match. The Java Language Specification specifies that void
is a special case.
In §15.27.3, it says:
A lambda expression is compatible in an assignment context, invocation context, or casting context with a target type T if T is a functional interface type (§9.8) and the expression is congruent with the function type of the ground target type derived from T.
We are in an invocation context here. T
is DoubleConsumer
. The ground target type derived from it is also DoubleConsumer
, and its function type is a method that takes a double
and returns void
.
Let's see what does "congruent" mean:
A lambda expression is congruent with a function type if all of the following are true:
- [...]
- If the lambda parameters are assumed to have the same types as the function type's parameter types, then:
- If the function type's result is
void
, the lambda body is either a statement expression (§14.8) or avoid
-compatible block.
"assumed to have the same types as the function type's parameter types" basically means that you did not explicitly write out the type of c
.
A statement expression is just an expression that can be made into a statement by adding a ;
at the end. Any method call is a statement expression. This is why the submit
call compiles.
5
is not a statement expression (but it is an expression), so c -> 5
does not compile.
If you think about it, by saying that methods that returns something should not be assigned to functional interfaces whose function type has a void return type, you are saying that "functions that take a A
and gives out a B
" are not a kind of "consumers of A
". However, they clearly are "consumers of A
"! They take in an A
after all. Whether or not something is a consumer of A
doesn't depend on what they produce.
Hence, Java is designed to allow this.