Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is necessary to explicitly say a lambda is Consumer to use andThen method?

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?

like image 865
Petar Petrov Avatar asked Jan 02 '23 00:01

Petar Petrov


2 Answers

Let's make this simpler:

 private static boolean test(String input){
       return input.equals("123");
 }

 Predicate<String> predicate = YourClass::test;
 Function<String, Boolean> function = YourClass::test;

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 a Consumer in this case, but it's a different story for the compiler.

like image 105
Eugene Avatar answered Jan 14 '23 15:01

Eugene


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 a Consumer so that you can chain an andThen() afterwards.

Explicitly casting this to a Consumer and using parentheses properly will let you achieve the desired effect:

List<Foo> foos = new ArrayList<>();
foos.forEach(((Consumer<Foo>) Null::doSomething).andThen(Null::doSomething2));

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.

like image 24
akortex Avatar answered Jan 14 '23 13:01

akortex