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

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

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

如果满足以下所有条件,则lambdaexpression式与函数types一致:

  • […]
  • 如果函数types的结果是void,那么lambda体是一个语句expression式(§14.8)或一个void兼容块。

所以lambda必须是一个语句expression式或一个void兼容的块:

  • 构造函数调用是一个语句expression式,所以它编译。
  • 一个string文字不是一个语句expression式,并且不是void兼容的(参见15.27.2中的例子 ),所以它不能编译。

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

然而, "hi"只是一个常数,并不会自己做任何事情。 在lambda体中唯一合理的做法是返回它。 但lambda方法将不得不返回types的StringObject ,但它返回void ,因此String cannot be casted to void错误。

第一种情况是可以的,因为你正在调用一个“特殊的”方法(一个构造函数),而你实际上并没有采用创build的对象。 只是为了更清楚一点,我会把可选的括号放在你的lambdas里:

 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" } }); 

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

JLS指定

如果函数types的结果是void,那么lambda体是一个语句expression式(§14.8)或一个void兼容块。

现在我们来详细看看,

由于你的takeBiConsumer方法是voidtypes的,所以接收new String("hi")的lambda将把它解释为一个块

 { new String("hi"); } 

这是无效的,因此第一个案件编译。

但是,在lambda是-> "hi"的情况下,块等

 { "hi"; } 

在java中是无效的语法。 因此,唯一要做的事情就是“嗨”,然后尝试并返回它。

 { return "hi"; } 

这是无效的,并解释错误信息

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

为了更好地理解,请注意,如果将takeBiConsumer的typestakeBiConsumer为一个string,则-> "hi"将是有效的,因为它只会尝试直接返回该string。


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

JLS 15.27

如果lambdaexpression式出现在除赋值上下文(第5.2节),调用上下文(第5.3节)或转换上下文(第5.5节)之外的某个程序中,则会出现编译时错误。

但是在我们的情况下,我们处于一个正确的调用环境中。