次の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」とマークされた行は正常にコンパイルされますが、「エラー」とマークされた行は失敗します。それらは本質的に同一に見えます。
ラムダはBiConsumer<String, String>
と一致する必要があります。 JLS#15.27.3(ラムダのタイプ) を参照する場合:
次のすべてに該当する場合、ラムダ式は関数型と一致します。
- [...]
- 関数型の結果がvoidの場合、ラムダ本体はステートメント式(§14.8)またはvoid互換ブロックのいずれかです。
そのため、ラムダはステートメント式またはvoid互換ブロックのいずれかでなければなりません。
基本的に、new String("hi")
は、実際に何かを実行する実行可能なコードです(新しい文字列を作成してから返します)。戻り値は無視できますが、new String("hi")
をvoid-return lambdaで使用して、新しい文字列を作成できます。
しかしながら、 "hi"
は、それ自体では何もしない単なる定数です。ラムダ本体でそれを行う唯一の合理的なことは、return itです。しかし、ラムダメソッドはString
またはObject
を返す必要がありますが、void
を返すため、String cannot be casted to void
エラー。
最初のケースは、「特別な」メソッド(コンストラクター)を呼び出しており、実際に作成されたオブジェクトを取得していないため、大丈夫です。わかりやすくするために、オプションのブレースをラムダに入れます。
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"
}
});
最初の場合はコンストラクターを実行していますが、作成したオブジェクトを返さず、2番目の場合は文字列値を返そうとしていますが、インターフェースBiConsumer
のメソッドはvoidを返すため、コンパイラーエラー。
JLSは、
関数型の結果がvoidの場合、ラムダ本体はステートメント式(§14.8)またはvoid互換ブロックのいずれかです。
それでは、詳細を見てみましょう。
takeBiConsumer
メソッドはvoid型なので、new String("hi")
を受け取るラムダはそれを次のようなブロックとして解釈します
{
new String("hi");
}
これはvoidで有効であるため、最初のケースはコンパイルされます。
ただし、ラムダが-> "hi"
の場合、次のようなブロック
{
"hi";
}
javaの有効な構文ではありません。したがって、 "hi"で行う唯一のことは、それを試して返すことです。
{
return "hi";
}
これはvoidでは無効であり、エラーメッセージを説明します
incompatible types: bad return type in lambda expression
Java.lang.String cannot be converted to void
理解を深めるために、takeBiConsumer
の型を文字列に変更した場合、-> "hi"
が有効になるのは、単に文字列を直接返そうとするためです。
最初は、ラムダが間違った呼び出しコンテキストにあることが原因でエラーが発生するので、この可能性をコミュニティと共有することに注意してください。
ラムダ式が、割り当てコンテキスト(§5.2)、呼び出しコンテキスト(§5.3)、またはキャストコンテキスト(§5.5)以外の場所のプログラムで発生した場合、コンパイル時エラーです。
ただし、この場合、 呼び出しコンテキスト にあり、これは正しいです。