Why does a Java method reference with return type match the Consumer interface?
I am confused by the following code
class LambdaTest {
public static void main(String[] args) {
Consumer<String> lambda1 = s -> {};
Function<String, String> lambda2 = s -> s;
Consumer<String> lambda3 = LambdaTest::consume; // but s -> s doesn't work!
Function<String, String> lambda4 = LambdaTest::consume;
}
static String consume(String s) { return s;}
}
I would have expected the assignment of lambda3 to fail as my consume method does not match the accept method in the Consumer Interface - the return types are different, String vs. void.
Moreover, I always thought that there is a one-to-one relationship between Lambda expressions and method references but this is clearly not the case as my example shows.
Could somebody explain to me what is happening here?
Solution 1:
As Brian Goetz pointed out in a comment, the basis for the design decision was to allow adapting a method to a functional interface the same way you can call the method, i.e. you can call every value returning method and ignore the returned value.
When it comes to lambda expressions, things get a bit more complicated. There are two forms of lambda expressions, (args) -> expression
and (args) -> { statements* }
.
Whether the second form is void
compatible, depends on the question whether no code path attempts to return a value, e.g. () -> { return ""; }
is not void
compatible, but expression compatible, whereas () -> {}
or () -> { return; }
are void
compatible. Note that () -> { for(;;); }
and () -> { throw new RuntimeException(); }
are both, void
compatible and value compatible, as they don’t complete normally and there’s no return
statement.
The form (arg) -> expression
is value compatible if the expression evaluates to a value. But there are also expressions, which are statements at the same time. These expressions may have a side effect and therefore can be written as stand-alone statement for producing the side effect only, ignoring the produced result. Similarly, the form (arg) -> expression
can be void
compatible, if the expression is also a statement.
An expression of the form s -> s
can’t be void
compatible as s
is not a statement, i.e. you can’t write s -> { s; }
either. On the other hand s -> s.toString()
can be void
compatible, because method invocations are statements. Similarly, s -> i++
can be void
compatible as increments can be used as a statement, so s -> { i++; }
is valid too. Of course, i
has to be a field for this to work, not a local variable.
The Java Language Specification §14.8. Expression Statements lists all expressions which may be used as statements. Besides the already mentioned method invocations and increment/ decrement operators, it names assignments and class instance creation expressions, so s -> foo=s
and s -> new WhatEver(s)
are void
compatible too.
As a side note, the form (arg) -> methodReturningVoid(arg)
is the only expression form that is not value compatible.
Solution 2:
consume(String)
method matches Consumer<String>
interface, because it consumes a String
- the fact that it returns a value is irrelevant, as - in this case - it is simply ignored. (Because the Consumer
interface does not expect any return value at all).
It must have been a design choice and basically a utility: imagine how many methods would have to be refactored or duplicated to match needs of functional interfaces like Consumer
or even the very common Runnable
. (Note that you can pass any method that consumes no parameters as a Runnable
to an Executor
, for example.)
Even methods like java.util.List#add(Object)
return a value: boolean
. Being unable to pass such method references just because that they return something (that is mostly irrelevant in many cases) would be rather annoying.