I've faced a strange(for me) behavior when I was trying to use function composition with two void methods. I've written a simple example to illustrate the problem :
public class Startup {
public static void main(String[] args) {
List<Foo> foos = new ArrayList<>();
// 1) Does not compile
foos.forEach(Startup::doSomething1.andThen(Startup::doSomething2));
Consumer<Foo> doSomething1 = Startup::doSomething1;
Consumer<Foo> doSomething2 = Startup::doSomething2;
// 2) Works as expected
foos.forEach(doSomething1.andThen(doSomething2));
}
public static void doSomething1(Foo foo) {
}
public static void doSomething2(Foo foo) {
}
public static class Foo {
}
}
When I try to compile the first solution it says "')' expected" before andThen call.
When I explicitly say this are Consumers the code is compiled and it works as expected.
Can anyone explain to my why this is happening and is there another way of doing function composition of void methods with Java 8?
This has to do with the way Java inferes, converts and detects types in lambdas. As mentioned in a comment above, the conversion to
Consumer<Foo>
has not taken place yet meaning that the compiler does not know that this is aConsumer
so that you can chain anandThen()
afterwards.Explicitly casting this to a
Consumer
and using parentheses properly will let you achieve the desired effect:I guess if you fiddle around with it, you can achieve the same behavior using type witnesses but I am not 100% sure whether they can achieve the desired result.
First time I noticed this was using chained comparators which may exhibit the same behavior. Doing an online search about that will show you some more intricate details regarding how this works.
Just as the Consumer mentioned:
And the functional interface gives us two methods:
void accept(T t)
default Consumer<T> andThen(Consumer<? super T> after)
As for
andThen(...)
:The Functional Interface is the syntactic sugar that Java 8 provides that we can just pass in a
lambda
ormethod reference
, and we can get more helpful/assistant features that we frequently need (default behaviors).And here, we can combine several functions altogether easily using
andThen
As for your case, you can just try something like this:
Output:
Let's make this simpler:
So a method reference is a poly expression (like generics for example), they depend on the context where they are used. So your
Startup::doSomething
method reference could be any@FunctionalInterface
that would comply to the that method. It might look to you that it is aConsumer
in this case, but it's a different story for the compiler.