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

为什么返回类型为的Java方法引用与使用者接口匹配?,java,java-8,Java,Java 8,我被下面的代码弄糊涂了 class LambdaTest { public static void main(String[] args) { Consumer<String> lambda1 = s -> {}; Function<String, String> lambda2 = s -> s; Consumer<String> lambda3 = Lam

我被下面的代码弄糊涂了

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;}
}
类LambdaTest{
公共静态void main(字符串[]args){
消费者lambda1=s->{};
函数lambda2=s->s;
Consumer lambda3=LambdaTest::Consumer;//但是s->s不起作用!
函数lambda4=LambdaTest::consume;
}
静态字符串使用(字符串s){return s;}
}
我本以为lambda3的赋值会失败,因为我的Consumer方法与Consumer接口中的accept方法不匹配-返回类型不同,String和void

此外,我一直认为Lambda表达式和方法引用之间存在一对一的关系,但这显然不是我的示例所示的情况

有人能解释一下这里发生了什么吗?

Consumer(String)
方法匹配
Consumer
接口,因为它使用一个
String
——它返回一个值的事实是不相关的,因为在本例中,它被忽略了。(因为
使用者
接口根本不期望任何返回值)

它一定是一种设计选择,基本上是一种实用工具:想象一下有多少方法需要重构或复制,以满足功能接口的需求,例如
消费者
,甚至是非常常见的
可运行
。(请注意,例如,您可以将任何不使用参数的方法作为
可运行的
传递给
执行器
。)

甚至像
java.util.List#add(Object)
这样的方法也会返回一个值:
boolean
。无法传递此类方法引用仅仅因为它们返回了某些内容(在许多情况下,这些内容基本上是不相关的),这将是一件非常烦人的事情。

正如所指出的,设计决策的基础是允许以调用方法的相同方式使方法适应功能接口,也就是说,您可以调用每个值返回方法并忽略返回的值

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

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

如果表达式的计算结果为值,则格式
(arg)->表达式
与值兼容。但也有表达式,它们同时是语句。这些表达式可能有副作用,因此可以编写为仅产生副作用的独立语句,而忽略产生的结果。类似地,如果表达式也是一个语句,则格式
(arg)->表达式
可以兼容
void

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

Java语言规范列出了可以用作语句的所有表达式。除了前面提到的方法调用和递增/递减运算符外,它还命名赋值和类实例创建表达式,因此
s->foo=s
s->newwhates
也兼容
void


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

关于
s->s
-我的猜测是,如果您在不引用现有方法的情况下显式定义新的lambda,则必须遵循其确切的方法签名。在这种情况下,
Consumer
不返回任何内容,因此不能用一行基本上代表
returns的行来结束lambda
s->s.toString()
(这将有相同的结果)应该可以工作,因为该方法将被简单地调用,其结果(
s
)将被忽略。
s->s
相当于
s->{return s;}
,它无法编译,因为
消费者
方法无法返回值
s->s.toString()
似乎“做”了同样的事情,但实际上它相当于
s->{s.toString();}
,它只调用一个方法,但不返回任何内容。在第一个示例中,您试图返回一个值。在第二种情况下,您实际上是在调用一个方法并忽略其结果。是的,就是这样。您希望我在原始答案中添加什么,还是可以接受它?这是这个设计决策的基础:Java允许您调用方法并忽略返回值(作为语句的方法调用表达式)。由于我们在调用时允许这样做,因此在将方法适配到参数兼容但函数接口无效返回的函数接口时也允许这样做。另外请注意,我们将进行其他适配(装箱、取消装箱),以使lambda的形状与函数接口的预期形状匹配。忽略返回值只是其中之一