Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a Java method reference with return type match the Consumer interface?

Tags:

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?

like image 604
Ulrich Schmidt Avatar asked May 18 '16 19:05

Ulrich Schmidt


People also ask

What is the use of Consumer interface in Java?

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) .

What is Consumer and BiConsumer in Java?

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.


1 Answers

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.

like image 122
Holger Avatar answered Oct 03 '22 13:10

Holger