为什么这个 Java 8 lambda 编译失败?

Why does this Java 8 lambda fail to compile?

以下 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" 的行却失败了。它们看起来本质上是相同的。

JLS 指定

If the function type's result is void, the lambda body is either a statement expression (§14.8) or a void-compatible block.

现在让我们详细看看,

由于您的 takeBiConsumer 方法是 void 类型,接收 new String("hi") 的 lambda 将把它解释为像

这样的块
{
    new String("hi");
}

无效,因此第一种情况编译。

然而,在lambda为-> "hi"的情况下,一个块如

{
    "hi";
}

是 java 中的无效语法。因此,对 "hi" 唯一要做的就是尝试 return 它。

{
    return "hi";
}

无效并解释错误信息

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

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


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

JLS 15.27

It is a compile-time error if a lambda expression occurs in a program in someplace other than an assignment context (§5.2), an invocation context (§5.3), or a casting context (§5.5).

然而在我们的例子中,我们处于 invocation context 这是正确的。

第一种情况是可以的,因为您正在调用 "special" 方法(构造函数)并且您实际上并没有获取创建的对象。只是为了更清楚,我将在您的 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"
    }
});

在第一种情况下,您正在执行一个构造函数,但您并未 return 创建对象,在第二种情况下,您正试图 return 一个字符串值,但您的方法在您的界面 BiConsumer return 无效,因此编译器错误。

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

然而,"hi" 只是一个常量,它自己不会做任何事情。在 lambda 主体中唯一合理的做法是 return 它。但是 lambda 方法必须 return 类型 StringObject,但它 returns void,因此 String cannot be casted to void 错误。

您的 lambda 需要与 BiConsumer<String, String> 一致。如果你参考JLS #15.27.3 (Type of a Lambda):

A lambda expression is congruent with a function type if all of the following are true:

  • [...]
  • If the function type's result is void, the lambda body is either a statement expression (§14.8) or a void-compatible block.

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