Why do Consumers accept lambdas with statement bodies but not expression bodies?
First, it's worth looking at what a Consumer<String>
actually is. From the documentation:
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.
So it's a function that accepts a String and returns nothing.
Consumer<String> p = ""::equals;
Compiles successfully because equals
can take a String (and, indeed, any Object). The result of equals is just ignored.*
p = s -> "".equals(s);
This is exactly the same, but with different syntax. The compiler knows not to add an implicit return
because a Consumer
should not return a value. It would add an implicit return
if the lambda was a Function<String, Boolean>
though.
p = s -> true;
This takes a String (s
) but because true
is an expression and not a statement, the result cannot be ignored in the same way. The compiler has to add an implicit return
because an expression can't exist on its own. Thus, this does have a return: a boolean. Therefore it's not a Consumer
.**
p = s -> ("".equals(s));
Again, this is an expression, not a statement. Ignoring lambdas for a moment, you will see the line System.out.println("Hello");
will similarly fail to compile if you wrap it in parentheses.
*From the spec:
If the body of a lambda is a statement expression (that is, an expression that would be allowed to stand alone as a statement), it is compatible with a void-producing function type; any result is simply discarded.
**From the spec (thanks, Eugene):
A lambda expression is congruent with a [void-producing] function type if ...
the lambda body is either a statement expression
(§14.8)
or a void-compatible block.
Why doesn't for-each method in java not throw an exception when a Function type argument is passed instead of Consumer?
Regarding why the first line works: there's an explanation in the specification:
Generally speaking, a lambda of the form
() -> expr
, whereexpr
is a statement expression, is interpreted as either() -> { return expr; }
or() -> { expr; }
, depending on the target type.
The above comes with the following example (good coincidence, this is very similar to your example):
// Consumer has a void result
java.util.function.Consumer<String> c = s -> list.add(s);
This simply means that the compiler ignores the return type for the expression, as though your code were simply this (which is valid for a void method):
Stream.of(1, 2, 3, 4).forEach(a -> {
a.equals(1);
});
And regarding the second line, the spec says:
A block lambda body is void-compatible if every return statement in the block has the form
return;
.
In your case, though, {return a.equals(1);}
does not meet this rule. Void methods don't return a value.
An easy way to understand this is to consider that the compiler applies method body validation rules (such that the body must be compatible with the declaration public void accept(T t)
) - as mentioned in the tutorial
Why can this lambda expression assigned to different functional interfaces?
The relevant part of the spec is Sec 15.27.3 (emphasis mine):
A lambda expression is congruent with a function type if all of the following are true:
The function type has no type parameters.
The number of lambda parameters is the same as the number of parameter types of the function type.
If the lambda expression is explicitly typed, its formal parameter types are the same as the parameter types of the function type.
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 a void-compatible block.
If the function type's result is a (non-void) type R, then either i) the lambda body is an expression that is compatible with R in an assignment context, or ii) the lambda body is a value-compatible block, and each result expression (§15.27.2) is compatible with R in an assignment context.
Your lambda body is a statement expression, and the function type's result is void.
In other words, it would be fine for you to write:
return0();
and ignore the return value in "regular" code, so it's fine to ignore the result value in a lambda too.
In terms of the question over ambiguity of overloads, there is no ambiguity in this case (it's easy to construct a case where there is ambiguity, e.g. another overload with a parameter that looks like Supplier
but is a different interface, i.e. takes no parameters, returns a value).
You would have to read the spec in detail for the precise reasoning, but I think the most relevant section is Sec 15.12, which describes method invocation expressions, and the most useful quote from that is in Sec 15.12.2.5, which deals with selecting the most-specific overload:
The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error.
You can use a Supplier<Integer>
in place of a Runnable
(with a bit of a hand-wavy fudge) because you can simply ignore the return value; you can't use a Runnable
in place of a Supplier<Integer>
because it doesn't have a return value.
So a method taking the Supplier<Integer>
is more specific than the method taking the Runnable
, hence that is the one which is invoked.
Why does this Java 8 lambda fail to compile?
Your lambda needs to be congruent with BiConsumer<String, String>
. If you refer to JLS #15.27.3 (Type of a Lambda):
A lambda expression is congruent with a function type if all of the following are true:
- [...]
- If the function type's result is void, the lambda body is either a statement expression (§14.8) or a void-compatible block.
So the lambda must either be a statement expression or a void compatible block:
- A constructor invocation is a statement expression so it compiles.
- A string literal isn't a statement expression and is not void compatible (cf. the examples in 15.27.2) so it does not compile.
Lambda Expression that has return type as void can compile with wrapper but not compile with primitive
Wrapper vs primitive is not the cause. The following will not compile either:
Integer i = 5;
A b = () -> i;
This is covered in JLS 15.27.3:
If the function type's result is void, the lambda body is either a statement expression (§14.8) or a void-compatible block.
5
is neither a statement not a void-compatible block. So A a = () -> 5;
will not compile.
The reason for this restriction is that void-returning lambda only makes sense if it operates by a side-effect. Simply returning a value like () -> 5
has no side effects and therefore such a lambda expression is definitely a bug.
What would be the problem in providing this keyword for Java's lambda body?
The Java Language Specification 15.27.2 says:
Unlike code appearing in anonymous class declarations, the meaning of names and the
this
andsuper
keywords appearing in a lambda body, along with the accessibility of referenced declarations, are the same as in the surrounding context (except that lambda parameters introduce new names).The transparency of
this
(both explicit and implicit) in the body of a lambda expression - that is, treating it the same as in the surrounding context - allows more flexibility for implementations, and prevents the meaning of unqualified names in the body from being dependent on overload resolution.Practically speaking, it is unusual for a lambda expression to need to talk about itself (either to call itself recursively or to invoke its other methods), while it is more common to want to use names to refer to things in the enclosing class that would otherwise be shadowed (
this
,toString()
). If it is necessary for a lambda expression to refer to itself (as if viathis
), a method reference or an anonymous inner class should be used instead.
Why does Java type inference fail to distinguish between Function and Consumer?
x -> _void()
and x -> one()
are expected to be compatible with Consumer<T>
(with the result of one()
to be discarded).
When the lambda body is of a block type, the compiler additionally checks the "return" compatibility.
The JLS is rather explicit about void/value compatibility for block bodies:
A block lambda body is void-compatible if every return statement in the block has the form return;.
A block lambda body is value-compatible if it cannot complete normally (§14.21) and every return statement in the block has the form return Expression;.
While that doesn't say why the single-expression bodies fail, it says exactly why block bodies compile: the compiler looks at the return
forms to judge on those bodies' compatibility with Consumer
or Function
(in this case).
For the method invocation expressions, the fact that this is allowed:
Consumer<Integer> c = x -> one(); //discarded result
Function<T, Integer> f = x -> one(); //returned result
doesn't enable the compiler to resolve the conflict that you observed. You can rewrite the same lambda expression with block bodies to resolve the conflict, and that's simply because block bodies are checked differently, by spec.
I guess I'm trying to say that the more natural question is "why block bodies compile at all in this case", given that we normally don't expect return types (forms?) to participate in overload resolution. But lambda expressions' congruence with types is something else, isn't it... I think this (that block type helps target type inference) is the special behavior.
Related Topics
Jtable Getselectedrow Does Not Return the Selected Row Index
Find Duplicate Element in Array in Time O(N)
How to Simulate a Real Mouse Click Using Java
Java Threads and Number of Cores
File Path Windows Format to Java Format
Steps Needed to Use MySQL Database with Play Framework 2.0
Using an Instance of an Object as a Key in Hashmap, and Then Access It with Exactly New Object
Passing Parameters to a Jdbc Preparedstatement
How to Create an 2D Arraylist in Java
How to Modify JSONnode in Java
Java Static Serialization Rules
How to Read an Aws S3 File with Java
Why Is System.Arraycopy Native in Java
Is There a Java Equivalent to C#'s 'Yield' Keyword
Using a Prepared Statement and Variable Bind Order by in Java with Jdbc Driver
What Is the Optimal Capacity and Load Factor for a Fixed-Size Hashmap