为什么这个Java 8 lambda无法编译?

Bri*_*don 83 java lambda compiler-errors void java-8

以下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
}
Run Code Online (Sandbox Code Playgroud)

编译器报告:

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

奇怪的是标记为"OK"的行编译得很好,但标记为"Error"的行失败了.他们看起来基本相同.

ass*_*ias 99

你的lambda需要与之一致BiConsumer<String, String>.如果您参考JLS#15.27.3(Lambda的类型):

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

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

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

  • @BrianGordon字符串文字是一个表达式(精确的常量表达式),但不是语句表达式. (31认同)

kaj*_*acx 44

基本上,new String("hi")是一段可执行的代码实际上做了某些事情(它创建一个新的String,然后返回它).返回的值可以忽略,new String("hi")仍可以在void-return lambda中使用以创建新的String.

但是,"hi"这只是一个常量,它不会对它自己做任何事情.在lambda体中唯一合理的做法是返回它.但lambda方法必须有返回类型StringObject,但它返回void,因此String cannot be casted to void错误.

  • 正确的正式术语是[*Expression Statement*](http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.8),实例创建表达式可能出现在两个地方,表达式或需要语句的地方,而`String`文字只是一个*表达式*,不能在*语句*上下文中使用. (6认同)
  • @ edc65:这就是为什么这个答案也得到了提升.规则的推理和非正式的直观解释可能确实有所帮助,但是,每个程序员都应该意识到它背后有正式的规则,并且在正式规则的结果*不直观可理解的情况下,正式规则将仍然胜利.例如`() - > x ++`是合法的,而`() - >(x ++)`,基本上完全一样,不是...... (3认同)
  • 公认的答案可能在形式上是正确的,但这是一个更好的解释 (2认同)

mor*_*ano 21

第一种情况是好的,因为你正在调用一个"特殊"方法(一个构造函数)而你实际上并没有采用创建的对象.为了更清楚,我将把可选的括号放在你的lambdas中:

takeBiConsumer((String s1, String s2) -> {new String("hi");}); // OK
takeBiConsumer((String s1, String s2) -> {"hi"}); // Error
Run Code Online (Sandbox Code Playgroud)

更清楚的是,我会将其翻译成较旧的符号:

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

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


Jea*_*ard 11

JLS指定了

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

现在让我们详细看一下

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

{
    new String("hi");
}
Run Code Online (Sandbox Code Playgroud)

这在void中是有效的,因此第一种情况是编译.

然而,在λ是的情况下,-> "hi"诸如的块

{
    "hi";
}
Run Code Online (Sandbox Code Playgroud)

在java中是无效的语法.因此,"hi"唯一要做的就是尝试归还它.

{
    return "hi";
}
Run Code Online (Sandbox Code Playgroud)

在void中无效并解释错误消息

incompatible types: bad return type in lambda expression
    java.lang.String cannot be converted to void
Run Code Online (Sandbox Code Playgroud)

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


请注意,起初我认为错误是由于lambda处于错误的调用上下文引起的,所以我将与社区分享这种可能性:

JLS 15.27

如果lambda表达式出现在除赋值上下文(第5.2节),调用上下文(第5.3节)或转换上下文(第5.5节)之外的某个位置的程序中,则为编译时错误.

但是在我们的例子中,我们处于一个正确的调用上下文中.