为什么返回类型的Java方法引用与Consumer接口匹配?

时间:2016-05-18 19:15:13

标签: java-8

我对以下代码感到困惑

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

我原本期望lambda3的赋值失败,因为我的consume方法与Consumer Interface中的accept方法不匹配 - 返回类型不同,String vs void。

此外,我一直认为Lambda表达式和方法引用之间存在一对一的关系,但显然并非如我的示例所示。

有人可以向我解释这里发生了什么吗?

2 个答案:

答案 0 :(得分:15)

Brian Goetz指出in a comment时,设计决策的基础是允许将方法调整为功能接口,就像调用方法一样,即可以调用每个值返回方法并忽略返回的值。

当谈到lambda表达式时,事情会变得复杂一些。 lambda表达式有两种形式,(args) -> expression(args) -> { statements* }

第二种形式是否void兼容,取决于是否没有代码路径尝试返回值的问题,例如() -> { return ""; }void不兼容,但与表达式兼容,而() -> {}() -> { return; }void兼容。请注意() -> { for(;;); }() -> { throw new RuntimeException(); }兼容,void兼容并且值兼容,因为它们无法正常完成且没有return语句。

如果表达式求值为某个值,则表单(arg) -> expression的值是兼容的。但是也有表达式,它们同时是语句。这些表达式可能有副作用,因此可以写为仅用于产生副作用的独立语句,忽略生成的结果。同样,如果表达式也是一个语句,则(arg) -> expression形式可以void兼容。

s -> s形式的表达式不能void兼容,因为s不是声明,即您也不能写s -> { s; }。另一方面,s -> s.toString()可以void兼容,因为方法调用是语句。同样,s -> i++可以void兼容,因为增量可以用作语句,因此s -> { i++; }也是有效的。当然,i必须是一个可以工作的字段,而不是局部变量。

Java语言规范§14.8. Expression Statements列出了可用作语句的所有表达式。除了已经提到的方法调用和递增/递减运算符之外,它还指定了赋值和类实例创建表达式,因此s -> foo=ss -> new WhatEver(s)也是void兼容的。

作为旁注,表单(arg) -> methodReturningVoid(arg)唯一表达式,与价值不兼容。

答案 1 :(得分:14)

consume(String)方法匹配Consumer<String>接口,因为它消耗String - 它返回一个值的事实是无关紧要的,因为 - 在这种情况下 - 它被简单地忽略。 (因为Consumer接口根本不期望任何返回值。

它必须是一个设计选择,基本上是一个实用程序:想象有多少方法必须重构或重复以匹配功能接口的需求,如Consumer甚至是非常常见的Runnable。 (请注意,您可以将任何不使用Runnable参数的方法传递给Executor。)

即使像java.util.List#add(Object)这样的方法也会返回一个值:boolean。无法传递这样的方法引用只是因为它们返回某些东西(在很多情况下大部分都是无关紧要的)会让人讨厌。