为什么这个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的String
或Object
,但它返回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节)之外的某个程序中,则会出现编译时错误。
但是在我们的情况下,我们处于一个正确的调用环境中。