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

Ulr*_*idt 19 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;}
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Hol*_*ger 19

作为布赖恩戈茨指出,在注释中,为设计决策的基础是允许调整的方法到功能界面,您可以调用该方法以同样的方式,即你可以调用每一个价值回归的方法,而忽略返回值.

说到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++; }必须是一个工作的领域,而不是一个局部变量.

Java语言规范§14.8.表达式语句列出了可用作语句的所有表达式.除了已经提到的方法调用和递增/递减运算,它的名字作业和课程实例创建表达式,所以is -> foo=ss -> new WhatEver(s)兼容了.

作为旁注,表单void唯一不兼容的表达形式.


Jus*_*bie 18

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

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

甚至像java.util.List#add(Object)返回值的方法:boolean.由于它们返回某些东西(在许多情况下几乎不相关)而无法传递此类方法引用会相当烦人.

  • 以下是此设计决策的基础:Java允许您*调用*方法并忽略返回值(方法调用表达式作为语句).由于我们在调用时允许这一点,因此我们在将方法适用于其参数兼容但功能接口为void返回的功能接口时也允许这样做. (18认同)
  • 另请注意,我们将进行其他修改(装箱,拆箱)以使lambda的形状与功能界面的预期形状相匹配.忽略返回值只是其中一种调整. (7认同)
  • `s - > s`相当于`s - > {return s; 因为`Consumer`方法无法返回值,因此无法编译.`s - > s.toString()`似乎"做"同样的事情,但它实际上相当于`s - > {s.toString(); }`,它只调用一个方法,但不返回任何内容.所以 - 在第一个例子中,你试图*返回*一个值.在第二个中,您实际上是在调用方法并忽略其结果. (3认同)
  • 关于`s - > s` - 我的猜测是,如果你明确定义一个新的lambda而不引用一个现有的方法,你*必须遵循其精确的方法签名.在这种情况下,`Consumer <String>`不会返回任何内容,所以你不能只用一条基本代表`return s;`的行来结束lambda.`s - > s.toString()`(它会有相同的结果)应该可以工作,因为方法只是被调用而且它的结果(`s`)会被忽略. (2认同)
  • 对,就是那样.您有什么想要我添加到原始答案中的内容,还是可以接受它? (2认同)