Why do Consumers accept lambdas with statement bodies but not expression bodies? - java

The following code surprisingly is compiling successfully:
Consumer<String> p = ""::equals;
This too:
p = s -> "".equals(s);
But this is fails with the error boolean cannot be converted to void as expected:
p = s -> true;
Modification of the second example with parenthesis also fails:
p = s -> ("".equals(s));
Is it a bug in Java compiler or is there a type inference rule I don't know about?

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.

I think the other answers complicate the explanation by focusing on lambdas whereas their behavior in this case is similar to the behavior of manually implemented methods. This compiles:
new Consumer<String>() {
#Override
public void accept(final String s) {
"".equals(s);
}
}
whereas this does not:
new Consumer<String>() {
#Override
public void accept(final String s) {
true;
}
}
because "".equals(s) is a statement but true is not. A lambda expression for a functional interface returning void requires a statement so it follows the same rules as a method's body.
Note that in general lambda bodies don't follow exactly the same rules as method bodies - in particular, if a lambda whose body is an expression implements a method returning a value, it has an implicit return. So for example, x -> true would be a valid implementation of Function<Object, Boolean>, whereas true; is not a valid method body. But in this particular case functional interfaces and method bodies coincide.

s -> "".equals(s)
and
s -> true
don't rely on same function descriptors.
s -> "".equals(s) may refer either String->void or String->boolean function descriptor.
s -> true refers to only String->boolean function descriptor.
Why ?
when you write s -> "".equals(s), the body of the lambda : "".equals(s) is a statement that produces a value.
The compiler considers that the function may return either void or boolean.
So writing :
Function<String, Boolean> function = s -> "".equals(s);
Consumer<String> consumer = s -> "".equals(s);
is valid.
When you assign the lambda body to a Consumer<String> declared variable, the descriptor String->void is used.
Of course, this code doesn't make much sense (you check the equality and you don't use the result) but the compiler doesn't care.
It is the same thing when you write a statement : myObject.getMyProperty() where getMyProperty() returns a boolean value but that you don't store the result of it.
when you write s -> true, the body of the lambda : true is a single expression .
The compiler considers that the function returns necessarily boolean.
So only the descriptor String->boolean may be used.
Now, come back to your code that doesn't compile.
What are you trying to do ?
Consumer<String> p = s -> true;
You cannot. You want to assign to a variable that uses the function descriptor Consumer<String> a lambda body with the String->void function descriptor.
It doesn't match !

Related

Convert from Boolean to BooleanSupplier

I have this function a:
public void a(BooleanSupplier param){}
that is called by function b:
public void b(Boolean param){
a(param)
}
The problem is that function "a" is expecting a BooleanSupplier but function b is sending a Boolean. I think I should convert a Boolean into a BooleanSupplier but I could not manage to convert one to another.
Let us take a closer look at the BooleanSupplier-interface. This is a functional interface, i.e. it has only one abstract method boolean getAsBoolean(). As we can see, the method has no parameters and returns a boolean.
Now let us look at the code presented. Method b receives one parameter Boolean param. method a receives one parameter of type BooleanSupplier. How can we convert the Boolean received by b to a BooleanSupplier? We just need to create a lambda that - when called - returns param. When written as lambda, this looks as follows:
only expression in the lambda -> return-value
^
|
() -> param;
|
v
empty parameter list
The minor type mismatch between Boolean (type of param) and boolean (expected return-type of BooleanSupplier) is resolved through autoboxing (oracle.com).
So in total, we can now call a as follows:
a(() -> param);
For further information on lambdas and their syntax, I recommend reading a tutorial on the topic, e.g. this one from oracle.com.

Why Flux.zip accept predefined Function but not an anonymous function?

when learning Flux (reactive-core) in java, I meet following questions about Function.
This is Flux.zip() method signature:
public static <I, O> Flux<O> zip(
final Function<? super Object[], ? extends O> combinator,
Publisher<?extends I>... sources) {
return zip(combinator, Queues.XS_BUFFER_SIZE, sources);
}
And when I try to invoke this method:
Flux<User> userFluxFromStringFlux(Flux<String> usernameFlux, Flux<String> firstnameFlux, Flux<String> lastnameFlux) {
// predefined function taking object[] and returns User
Function<Object[], User> function = array -> new User(array[0].toString(),array[1].toString(), array[2].toString());
// it is working without error
Flux.zip(function, usernameFlux, firstnameFlux, lastnameFlux);
// this is also working without error
Flux.zip(array -> {
return new User(array[0].toString(),array[1].toString(), array[2].toString());
}, usernameFlux, firstnameFlux, lastnameFlux);
// but this has error with array[0] "Array type expected; found: 'org.reactivestreams.subscriber<capture<? super java.lang.object>>'"
Flux.zip(array -> new User(array[0].toString(),array[1].toString(), array[2].toString()), usernameFlux, firstnameFlux, lastnameFlux);
return null;
}
The third way which using anonymous function, but IDEA reports that there is a error :
Array type expected; found: 'org.reactivestreams.subscriber>.
I wonder why predefined Function and anonymous function with explict return is working but anonymous function?
I appreciate your help.
Not a compiler expert, but I think it has to do with the java compiler seeing an ambiguity with the short form lambda: is what you are passing an inline Publisher (since it is a functional interface) or a Function?
This confusion is made possible because the short form doesn't have an explicit return statement: in the case of the Publisher option it would mean you create a User to immediately be garbaged collected, but that's not the sort of things the compiler will forbid you to do.
So the target type of the lambda is assumed to be Publisher, and thus array is inferred to be a Subscriber. But then the array index operator is used on it, which surely must be wrong.
On the other hand, putting in the brackets { } removes that ambiguity by having an explicit return type that seem to be used in the inference. To the compiler you can no longer be representing a Publisher, so the next candidate (Function) is used.
Another way of removing the ambiguity is to show the compiler that the array is... an array:
Flux.zip((Object[] array) -> new User(array[0].toString(),array[1].toString(), array[2].toString())
, usernameFlux, firstnameFlux, lastnameFlux);

java 8 lambdas interface mismatch [duplicate]

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

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

Call a method on the return value of a method reference

I have a stream of files that I want to filter based on the ending of the file name:
public Stream<File> getFiles(String ending) throws IOException {
return Files.walk(this.path)
.filter(Files::isRegularFile)
.map(Path::toFile)
.filter(file -> file.getName().endsWith(ending));
}
While the lambda in the last line is not bad, I thought I could use method references there as well, like so:
.filter(File::getName.endsWith(ending));
Or alternatively wrapped in parentheses. However, this fails with The target type of this expression must be a functional interface
Can you explain why this doesn't work?
Can you explain why this doesn't work?
Method references are syntactical sugar for a lambda expression. For example, the method reference File::getName is the same as (File f) -> f.getName().
Lambda expressions are "method literals" for defining the implementation of a functional interface, such as Function, Predicate, Supplier, etc.
For the compiler to know what interface you are implementing, the lambda or method reference must have a target type:
// either assigned to a variable with =
Function<File, String> f = File::getName;
// or assigned to a method parameter by passing as an argument
// (the parameter to 'map' is a Function)
...stream().map(File::getName)...
or (unusually) cast to something:
((Function<File, String>) File::getName)
Assignment context, method invocation context, and cast context can all provide target types for lambdas or method references. (In all 3 of the above cases, the target type is Function<File, String>.)
What the compiler is telling you is that your method reference does not have a target type, so it doesn't know what to do with it.
File::getName is a method reference and String::endsWith is as well. However they cannot be chained together. You could create another method to do this
public static Predicate<File> fileEndsWith(final String ending) {
return file -> file.getName().endsWith(ending);
}
and then use it
.filter(MyClass.fileEndsWith(ending))
This doesn't buy you much if you're not re-using it though.
A couple of helpers might assist in providing some semblance of what you wish for. Using the helpers below, you can replace your lambda with an expression containing method references, like this:
// since your predicate is on the result of a function call, use this to create a predicate on the result of a function
public static <A,B> Predicate<A> onResult(Function<A,B> extractor, Predicate<B> predicate){
return a -> predicate.test(extractor.apply(a));
}
// since your predicate involves an added parameter, use this to reduce the BiPredicate to a Predicate with one less parameter
public static <T,U> Predicate<T> withParam(BiPredicate<T,U> pred, U param){
return t -> pred.test(t,param);
}
public Stream<File> getFiles(String ending) throws IOException {
return Files.walk(Paths.get("."))
.filter(Files::isRegularFile)
.map(Path::toFile)
.filter(onResult(File::getName, withParam(String::endsWith, ending)));
}

Categories