我对以下代码感到困惑
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表达式和方法引用之间存在一对一的关系,但显然并非如我的示例所示。
有人可以向我解释这里发生了什么吗?
答案 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=s
和s -> 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
。无法传递这样的方法引用只是因为它们返回某些东西(在很多情况下大部分都是无关紧要的)会让人讨厌。