Why is necessary to explicitly say a lambda is Con

2019-02-24 09:47发布

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?

3条回答
家丑人穷心不美
2楼-- · 2019-02-24 09:47

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.

查看更多
爷的心禁止访问
3楼-- · 2019-02-24 09:51

Just as the Consumer mentioned:

This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.

And the functional interface gives us two methods:

  1. void accept(T t)
  2. default Consumer<T> andThen(Consumer<? super T> after)

As for andThen(...):

Returns a composed Consumer that performs, in sequence, this operation followed by the after operation.

The Functional Interface is the syntactic sugar that Java 8 provides that we can just pass in a lambda or method 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:

public class CastToFunctionalInterface {
    public static void main(String... args) {
        ((Consumer<Integer>) CastToFunctionalInterface::consumeInteger)
                .andThen(CastToFunctionalInterface::consumeAnotherInteger)
                .accept(10);
    }

    private static void consumeInteger(Integer a) {
        System.out.println("I'm an Integer: " + a);
    }

    private static void consumeAnotherInteger(Integer b) {
        System.out.println("I'm another integer: " + b);
    }
}

Output:

I'm an Integer: 10
I'm another integer: 10
查看更多
家丑人穷心不美
4楼-- · 2019-02-24 09:57

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.

查看更多
登录 后发表回答