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?
Interface Consumer<T>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. This is a functional interface whose functional method is accept(Object) .
In Java terms, a Consumer is an idiom for a void method. 'setter' methods are good examples of consumers. A BiConsumer is similar to the consumer and it accepts two inputs and does not return anything.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With