【问题标题】:Why does this Java 8 lambda fail to compile?为什么这个 Java 8 lambda 编译失败?
【发布时间】:2015-05-29 11:56:58
【问题描述】:

以下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”的行编译失败。它们看起来基本相同。

【问题讨论】:

  • 函数式接口方​​法返回void是不是这里打错了?
  • @NathanHughes 不。事实证明它是问题的核心 - 请参阅已接受的答案。
  • takeBiConsumer{ } 内是否应该有代码...如果是这样,您能举个例子...如果我没看错的话,bc 是类/接口BiConsumer,因此应该包含一个名为accept的方法来匹配接口签名……如果这是正确的,那么accept方法需要在某个地方定义(例如一个类实现了接口)......那么{}中应该是什么?? ... ... ... 谢谢
  • 具有单个方法的接口可以与 Java 8 中的 lambda 互换。在这种情况下,(String s1, String s2) -&gt; "hi" 是 BiConsumer 的一个实例。

标签: java lambda compiler-errors java-8 void


【解决方案1】:

JLS 指定

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

现在让我们详细了解一下,

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

{
    new String("hi");
}

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

但是,在 lambda 为 -&gt; "hi" 的情况下,一个块如

{
    "hi";
}

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

{
    return "hi";
}

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

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

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


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

JLS 15.27

如果程序中出现 lambda 表达式,则为编译时错误 在赋值上下文(§5.2)以外的某个地方,调用 上下文(第 5.3 节)或强制转换上下文(第 5.5 节)。

但是在我们的例子中,我们是在invocation context 中,这是正确的。

【讨论】:

    【解决方案2】:

    您的 lambda 需要与 BiConsumer&lt;String, String&gt; 一致。如果参考JLS #15.27.3 (Type of a Lambda)

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

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

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

    【讨论】:

    • @BrianGordon 字符串文字是一个表达式(准确地说是一个常量表达式),但不是语句表达式。
    【解决方案3】:

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

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

    【讨论】:

    • 正确的正式术语是Expression Statement,实例创建表达式可能出现在需要表达式或语句的两个地方,而String文字只是一个表达式 不能在 statement 上下文中使用。
    • 接受的答案可能在形式上是正确的,但这是一个更好的解释
    • @edc65:这就是为什么这个答案也得到了赞成。规则的推理和非形式的直观解释可能确实有帮助,但是,每个程序员都应该意识到它背后有形式规则,并且在形式规则的结果直观可理解的情况下,正式规则仍然会获胜。例如。 ()-&gt;x++ 是合法的,而()-&gt;(x++),基本上做的完全一样,不是……
    【解决方案4】:

    第一种情况是可以的,因为您正在调用“特殊”方法(构造函数)并且您实际上并没有获取创建的对象。为了更清楚起见,我将把可选的大括号放在你的 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"
        }
    });
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-06
      • 1970-01-01
      • 1970-01-01
      • 2013-05-14
      相关资源
      最近更新 更多