web-dev-qa-db-ja.com

なぜこのJava 8ラムダがコンパイルに失敗しますか?

次の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」とマークされた行は正常にコンパイルされますが、「エラー」とマークされた行は失敗します。それらは本質的に同一に見えます。

82
Brian Gordon

ラムダはBiConsumer<String, String>と一致する必要があります。 JLS#15.27.3(ラムダのタイプ) を参照する場合:

次のすべてに該当する場合、ラムダ式は関数型と一致します。

  • [...]
  • 関数型の結果がvoidの場合、ラムダ本体はステートメント式(§14.8)またはvoid互換ブロックのいずれかです。

そのため、ラムダはステートメント式またはvoid互換ブロックのいずれかでなければなりません。

  • コンストラクターの呼び出しは ステートメント式 なので、コンパイルされます。
  • 文字列リテラルはステートメント式ではなく、void互換性がないため( 15.27.2の例 を参照)、コンパイルされません。
99
assylias

基本的に、new String("hi")は、実際に何かを実行する実行可能なコードです(新しい文字列を作成してから返します)。戻り値は無視できますが、new String("hi")をvoid-return lambdaで使用して、新しい文字列を作成できます。

しかしながら、 "hi"は、それ自体では何もしない単なる定数です。ラムダ本体でそれを行う唯一の合理的なことは、return itです。しかし、ラムダメソッドはStringまたはObjectを返す必要がありますが、voidを返すため、String cannot be casted to voidエラー。

43
kajacx

最初のケースは、「特別な」メソッド(コンストラクター)を呼び出しており、実際に作成されたオブジェクトを取得していないため、大丈夫です。わかりやすくするために、オプションのブレースをラムダに入れます。

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を返すため、コンパイラーエラー。

21
morgano

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"が有効になるのは、単に文字列を直接返そうとするためです。


最初は、ラムダが間違った呼び出しコンテキストにあることが原因でエラーが発生するので、この可能性をコミュニティと共有することに注意してください。

JLS 15.27

ラムダ式が、割り当てコンテキスト(§5.2)、呼び出しコンテキスト(§5.3)、またはキャストコンテキスト(§5.5)以外の場所のプログラムで発生した場合、コンパイル時エラーです。

ただし、この場合、 呼び出しコンテキスト にあり、これは正しいです。

11