为什么这个 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 处于错误的调用上下文中引起的,因此我将与社区分享这种可能性:
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 类型 String
或 Object
,但它 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 兼容块:
- 构造函数调用是 a statement expression 所以它编译。
- 字符串文字不是语句表达式,不兼容 void(参见 the examples in 15.27.2),因此无法编译。
以下 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 处于错误的调用上下文中引起的,因此我将与社区分享这种可能性:
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 类型 String
或 Object
,但它 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 兼容块:
- 构造函数调用是 a statement expression 所以它编译。
- 字符串文字不是语句表达式,不兼容 void(参见 the examples in 15.27.2),因此无法编译。