为什么消费者接受带有语句体的 lambda,但不接受表达式主体?

2022-08-31 15:54:20

令人惊讶的是,以下代码已成功编译:

Consumer<String> p = ""::equals;

这也是:

p = s -> "".equals(s);

但这是失败的,错误如预期:boolean cannot be converted to void

p = s -> true;

修改带括号的第二个示例也失败:

p = s -> ("".equals(s));

这是Java编译器中的错误还是有我不知道的类型推断规则?


答案 1

首先,值得一看的是实际上是什么。从文档中Consumer<String>

表示接受单个输入参数且不返回任何结果的操作。与大多数其他功能接口不同,消费者应该通过副作用进行操作。

因此,它是一个接受字符串但不返回任何内容的函数。

Consumer<String> p = ""::equals;

编译成功,因为可以接受字符串(以及任何对象)。相等的结果将被忽略。equals

p = s -> "".equals(s);

这是完全相同的,但具有不同的语法。编译器知道不要添加隐式,因为 a 不应该返回值。如果 lambda 是一个虽然,它将添加一个隐式。returnConsumerreturnFunction<String, Boolean>

p = s -> true;

这需要一个 String (),但由于它是一个表达式而不是一个语句,所以不能以相同的方式忽略结果。编译器必须添加隐式,因为表达式不能单独存在。因此,这确实有一个返回值:布尔值。因此,它不是.**struereturnConsumer

p = s -> ("".equals(s));

同样,这是一个表达式,而不是一个语句。暂时忽略 lambdas,如果您将该行括在括号中,您将看到该行同样无法编译。System.out.println("Hello");


*从规格

如果 lambda 的主体是语句表达式(即允许作为语句独立存在的表达式),则它与产生 void 的函数类型兼容;任何结果都会被简单地丢弃。

**来自规范(谢谢,尤金):

lambda 表达式与 [产生 void 的] 函数类型一致,如果 ...lambda 主体可以是语句表达式 (§14.8) 或与 void 兼容的块。


答案 2

我认为其他答案通过关注lambdas使解释复杂化,而在这种情况下,它们的行为类似于手动实现方法的行为。这将编译:

new Consumer<String>() {
    @Override
    public void accept(final String s) {
        "".equals(s);
    }
}

而这不会:

new Consumer<String>() {
    @Override
    public void accept(final String s) {
        true;
    }
}

因为是一个陈述,但不是。返回 void 的功能接口的 lambda 表达式需要一个语句,因此它遵循与方法主体相同的规则。"".equals(s)true

请注意,一般来说,lambda 主体不遵循与方法主体完全相同的规则 - 特别是,如果主体是表达式的 lambda 实现返回值的方法,则它具有隐式 。例如,将是 的有效实现,而 不是有效的方法体。但在这种特殊情况下,功能接口和方法主体是一致的。returnx -> trueFunction<Object, Boolean>true;


推荐