【问题标题】:Why is necessary to explicitly say a lambda is Consumer to use andThen method?为什么有必要明确地说 lambda 是 Consumer 以使用 andThen 方法?
【发布时间】:2019-01-21 00:31:00
【问题描述】:

当我尝试将函数组合与两个 void 方法一起使用时,我遇到了一个奇怪的(对我来说)行为。我写了一个简单的例子来说明这个问题:

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 {

    }

}

当我尝试编译第一个解决方案时,它会在然后调用之前显示“')' 预期”。

当我明确说这是消费者时,代码已编译并按预期工作。

谁能向我解释为什么会发生这种情况,是否有另一种方法可以使用 Java 8 对 void 方法进行函数组合?

【问题讨论】:

  • 期望Startup::doSomething1 严格来说是Consumer&lt;Foo&gt;?不,也可以是interface FooTaker{void takeFoo(Foo f);}...不过在此之前,这是语法问题...
  • 我的猜测是,从方法句柄到Consumer&lt;Foo&gt; 的隐式转换在您使用. 令牌时并没有发生;显式转换可能会起作用。

标签: java lambda java-8 functional-programming


【解决方案1】:

让我们简化一下:

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

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

所以方法引用是一个 poly 表达式(例如泛型),它们取决于使用它们的上下文。因此,您的 Startup::doSomething 方法引用可以是 any @FunctionalInterface,它符合该方法。在这种情况下,您可能会认为它是 Consumer,但对于编译器来说却是另一回事。

【讨论】:

    【解决方案2】:

    正如Consumer 提到的那样:

    这是一个函数式接口,因此可以用作 lambda 表达式或方法引用的赋值目标

    功能接口给了我们两种方法:

    1. void accept(T t)
    2. default Consumer&lt;T&gt; andThen(Consumer&lt;? super T&gt; after)

    至于andThen(...)

    返回一个组合消费者,它按顺序执行此操作,然后执行 after 操作。

    Functional Interface 是 Java 8 提供的语法糖,我们只需传入 lambdamethod reference,就可以获得更多我们经常需要的有用/辅助功能(默认行为)。

    在这里,我们可以使用andThen轻松组合多个功能

    至于你的情况,你可以试试这样的:

    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);
        }
    }
    

    输出:

    I'm an Integer: 10
    I'm another integer: 10
    

    【讨论】:

      【解决方案3】:

      这与 Java 在 lambda 中推断、转换和检测类型的方式有关。正如上面评论中提到的,到Consumer&lt;Foo&gt; 的转换尚未发生,这意味着编译器不知道这是Consumer,因此您可以在之后链接andThen()

      将其显式转换为 Consumer 并正确使用括号将使您达到预期的效果:

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

      我想如果你摆弄它,你可以使用类型见证来实现相同的行为,但我不能 100% 确定它们是否能达到预期的结果。

      我第一次注意到这是使用可能表现出相同行为的链式比较器。对此进行在线搜索将向您展示有关其工作原理的更复杂的细节。

      【讨论】:

        猜你喜欢
        • 2014-03-07
        • 1970-01-01
        • 2012-11-29
        • 1970-01-01
        • 1970-01-01
        • 2019-10-08
        • 2016-01-09
        • 2010-12-25
        相关资源
        最近更新 更多