为什么此Java 8 lambda无法编译?


85

以下Java代码无法编译:

@FunctionalInterface
private interface BiConsumer<A, B> {
    void accept(A a, B b);
}

private static void takeBiConsumer(BiConsumer<String, String> bc) { }

public static void main(String[] args) {
    takeBiConsumer((String s1, String s2) -> new String("hi")); // OK
    takeBiConsumer((String s1, String s2) -> "hi"); // Error
}

编译器报告:

Error:(31, 58) java: incompatible types: bad return type in lambda expression
    java.lang.String cannot be converted to void

奇怪的是,标记为“ OK”的行可以正常编译,但是标记为“ Error”的行失败。它们看起来基本相同。


5
功能接口方法返回void是错字吗?
内森·休斯

6
@NathanHughes没有。事实证明,这是问题的核心-查看已接受的答案。
Brian Gordon

应该有内部的代码{ }takeBiConsumer......如果是这样,你能不能举一个例子...如果我读这正确的,bc是类/接口的实例BiConsumer,因此应包含一个名为方法accept相匹配的接口签名。 ......如果这是正确的,那么该accept方法需要定义的地方(例如,一个类实现了接口)...所以是应该在什么{}?…………谢谢
dsdsdsdsd

具有单个方法的接口可以与Java 8中的lambda互换。在这种情况下,它(String s1, String s2) -> "hi"是BiConsumer <String,String>的实例。
Brian Gordon

Answers:


100

您的lambda需要与保持一致BiConsumer<String, String>。如果您参考JLS#15.27.3(Lambda的类型)

如果满足以下所有条件,则lambda表达式与函数类型一致:

  • [...]
  • 如果函数类型的结果为void,则lambda主体为语句表达式(第14.8节)或与void兼容的块。

因此,lambda必须是语句表达式或与void兼容的块:


31
@BrianGordon字符串文字是一个表达式(精确来说是一个常量表达式),而不是一个语句表达式。
assylias,2015年

44

基本上,new String("hi")是一段可执行的代码,实际上可以执行某项操作(它会创建一个新的String,然后将其返回)。返回的值可以忽略,new String("hi")仍可以在void-return lambda中使用,以创建新的String。

但是,"hi"这只是一个常量,它本身不会执行任何操作。在lambda主体中唯一与之相关的合理事情是将其返回。但是lambda方法必须具有返回类型StringObject,但是它返回void,因此是String cannot be casted to void错误的。


6
正确的正式用语是表达式语句,实例创建表达式可以出现在两个地方,其中一个表达式或需要声明,而String文字只是一种表达,不能被用在语句的上下文。
Holger 2015年

2
公认的答案可能在形式上是正确的,但这是一个更好的解释
edc65

3
@ edc65:这就是为什么这个答案也被赞成的原因。规则的推理和非形式的直观解释可能确实有帮助,但是,每个程序员都应意识到背后存在形式规则,并且在形式规则的结果无法直观理解的情况下,形式规则仍将获胜。例如()->x++,法律是合法的,而实际上()->(x++)做的完全一样,却不合法……
Holger 2015年

21

第一种情况是可以的,因为您正在调用“特殊”方法(构造函数),而实际上并没有使用创建的对象。为了更清楚一点,我将可选的花括号放在您的lambda中:

takeBiConsumer((String s1, String s2) -> {new String("hi");}); // OK
takeBiConsumer((String s1, String s2) -> {"hi"}); // Error

更清楚的是,我将其转换为较旧的表示法:

takeBiConsumer(new BiConsumer<String, String>(String s1, String s2) {
    public void accept(String s, String s2) {
        new String("hi"); // OK
    }
});

takeBiConsumer(new BiConsumer<String, String>(String s1, String s2) {
    public void accept(String s, String s2) {
        "hi"; // Here, the compiler will attempt to add a "return"
              // keyword before the "hi", but then it will fail
              // with "compiler error ... bla bla ...
              //  java.lang.String cannot be converted to void"
    }
});

在第一种情况下,您正在执行构造函数,但没有返回创建的对象,在第二种情况下,您试图返回String值,但是接口中的方法BiConsumer返回void,因此出现编译器错误。


12

JLS指定

如果函数类型的结果为void,则lambda主体为语句表达式(第14.8节)或与void兼容的块。

现在让我们详细了解一下

由于您的takeBiConsumer方法是空类型,因此lambda接收new String("hi")会将其解释为一个块,例如

{
    new String("hi");
}

这在void中有效,因此第一种情况可以编译。

然而,在拉姆达是的情况下-> "hi",一个块如

{
    "hi";
}

在Java中不是有效的语法。因此,与“ hi”有关的唯一事情就是尝试将其返回。

{
    return "hi";
}

在无效的地方无效并解释错误消息

incompatible types: bad return type in lambda expression
    java.lang.String cannot be converted to void

为了更好地理解,请注意,如果将类型更改takeBiConsumer为字符串,-> "hi"则将是有效的,因为它只会尝试直接返回字符串。


请注意,起初我强调该错误是由于lambda在错误的调用上下文中引起的,因此我将与社区分享这种可能性:

捷豹路虎15.27

如果lambda表达式出现在程序中的某个环境中,而不是赋值上下文(第5.2节),调用上下文(第5.3节)或强制转换上下文(第5.5节),则这是编译时错误。

但是,就我们而言,我们处于正确的调用上下文中。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.