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

Zef*_*ick 61 java lambda type-inference java-8

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

Consumer<String> p = ""::equals;
Run Code Online (Sandbox Code Playgroud)

这个也是:

p = s -> "".equals(s);
Run Code Online (Sandbox Code Playgroud)

但是这会因boolean cannot be converted to void预期错误而失败:

p = s -> true;
Run Code Online (Sandbox Code Playgroud)

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

p = s -> ("".equals(s));
Run Code Online (Sandbox Code Playgroud)

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

Mic*_*ael 80

首先,值得看一下Consumer<String>实际情况.从文档:

表示接受单个输入参数但不返回结果的操作.与大多数其他功能接口不同,消费者需要通过副作用进行操作.

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

Consumer<String> p = ""::equals;
Run Code Online (Sandbox Code Playgroud)

编译成功因为equals可以采用String(实际上是任何Object).等于的结果只是被忽略了.*

p = s -> "".equals(s);
Run Code Online (Sandbox Code Playgroud)

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

p = s -> true;
Run Code Online (Sandbox Code Playgroud)

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

p = s -> ("".equals(s));
Run Code Online (Sandbox Code Playgroud)

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


*从规格:

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

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

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

  • @FedericoPeraltaSchaffner它不是一个定义它的编译器,而是JLS :)(编译器必须符合).如果语句编译器的所有表达式都无法报告大量错误.如果必须使用非void函数的所有结果,则需要大量不必要的赋值/附加局部变量,只需考虑`StringBuilder.append(..)`作为开始.很久以来就是这样,可能是从Java 1.0开始 (4认同)
  • @Zefick是的,关键的区别在于语句可能有也可能没有隐含的`return`(基于推理),而表达式总是有一个隐含的`return`.它导致更好的语法. (3认同)
  • @Michael有一天,我想知道*为什么*语言中的语句和表达之间存在区别.我真的不明白,只是让一切都成为陈述...... (2认同)

Rei*_*ica 11

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

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

而这不是:

new Consumer<String>() {
    @Override
    public void accept(final String s) {
        true;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • "这里没有什么关于lambdas的特别之处".你大多是对的,不过`` - >.".equals(s)`可以满足'Consumer <String>`**和**`Function <String,Boolean>`我认为这是混乱的根源. (4认同)

dav*_*xxx 8

s -> "".equals(s)
Run Code Online (Sandbox Code Playgroud)

s -> true
Run Code Online (Sandbox Code Playgroud)

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

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);
Run Code Online (Sandbox Code Playgroud)

已验证.

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

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

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

Consumer<String> p = s -> true;
Run Code Online (Sandbox Code Playgroud)

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