为什么消费者接受语句体而不是表达体的lambdas?

时间:2017-08-02 12:27:40

标签: java lambda java-8 type-inference

以下代码令人惊讶地成功编译:

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

这也是:

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

但是失败了,错误boolean cannot be converted to void正如预期的那样:

p = s -> true;

使用括号修改第二个示例也失败:

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

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

3 个答案:

答案 0 :(得分:80)

首先,值得看一下Consumer<String>实际上是什么。 From the documentation

  

表示接受单个输入参数的操作   不返回结果。不像大多数其他功能接口,消费者   预计会通过副作用来运作。

所以它是一个接受String并且不返回任何内容的函数。

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

成功编译,因为equals可以获取字符串(实际上是任何对象)。等于的结果只是被忽略了。*

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

这完全相同,但语法不同。编译器知道不添加隐式return,因为Consumer不应返回值。如果lambda是return,它添加隐式Function<String, Boolean>

p = s -> true;

这需要一个String(s),但因为true是一个表达式而不是一个语句,所以不能以相同的方式忽略结果。编译器必须添加隐式return,因为表达式本身不能存在。因此,这个 有一个返回:一个布尔值。因此,它不是Consumer。**

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

同样,这是一个表达式,而不是一个语句。暂时忽略lambdas,如果用括号括起来,你会发现行System.out.println("Hello");同样无法编译。

*来自the spec

  

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

**来自the spec(谢谢,Eugene):

  

如果......,lambda表达式与[void-producing]函数类型一致   lambda body是一个语句表达式   (§14.8)   或与空白兼容的块。

答案 1 :(得分:11)

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

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

而这不是:

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

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

请注意,一般情况下,lambda实体不会完全遵循 与方法体相同的规则 - 特别是,如果一个lambda,其body是一个表达式,它实现了一个返回值的方法,它具有一个隐式return。例如,x -> true将是Function<Object, Boolean>的有效实现,而true;不是有效的方法体。但在这种特殊情况下,功能接口和方法体重合。

答案 2 :(得分:8)

s -> "".equals(s)

s -> true

不要依赖相同的函数描述符。

s -> "".equals(s)可以引用String->voidString->boolean函数描述符 s -> true仅指String->boolean函数描述符。

为什么?

  • 当你写s -> "".equals(s)时,lambda的主体:"".equals(s) 是一个产生值的声明
    编译器认为该函数可能返回voidboolean

所以写作:

Function<String, Boolean> function = s -> "".equals(s);
Consumer<String> consumer = s -> "".equals(s);

有效。

将lambda主体分配给Consumer<String>声明的变量时,使用描述符String->void
当然,这段代码没有多大意义(你检查相等性而不使用结果)但是编译器并不关心。
在撰写陈述时也是如此:myObject.getMyProperty()其中getMyProperty()返回boolean值但您不存储其结果。

  • 当你写s -> true时,lambda的主体:true 是单个表达式
    编译器认为函数返回必然 boolean 因此,只能使用描述符String->boolean

现在,回到你不编译的代码 你想做什么?

Consumer<String> p = s -> true;

你做不到。您希望为使用函数描述符Consumer<String>的变量分配一个带有String->void函数描述符的lambda主体。 它不匹配!