このJava8ラムダがコンパイルに失敗するのはなぜですか?


85

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


5
機能インターフェイスメソッドがvoidを返すのはここでのタイプミスですか?
ネイサンヒューズ

6
@NathanHughesいいえ。それは質問の中心であることが判明しました-受け入れられた答えを参照してください。
ブライアンゴードン

内部のコードがあるはず{ }takeBiConsumer...もしそうなら、私はこれを正しく読めば、あなたが例を与えることができる...、bcクラス/インタフェースのインスタンスであるBiConsumerため、呼び出されたメソッド含まれている必要がありaccept、インタフェース署名に一致するようにします。 .. ...そしてそれが正しい場合は、acceptメソッドをどこかに定義する必要があります(たとえば、インターフェイスを実装するクラス)...したがって、{}?? ... ... ...ありがとう
dsdsdsdsd 2016

単一のメソッドを持つインターフェースは、Java 8のラムダと交換可能です。この場合、(String s1, String s2) -> "hi"はBiConsumer <String、String>のインスタンスです。
ブライアンゴードン

回答:


100

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

次のすべてが当てはまる場合、ラムダ式は関数型と合同です。

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

したがって、ラムダはステートメント式またはvoid互換ブロックのいずれかである必要があります。


31
@BrianGordon文字列リテラルは式(正確には定数式)ですが、ステートメント式ではありません。
assylias 2015年

44

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

ただし、これ"hi"は定数であり、それ自体では何もしません。ラムダ本体でそれを行う唯一の合理的なことは、それを返すことです。ただし、ラムダメソッドは戻り値の型Stringまたはを持っている必要Objectがありますが、を返すvoidため、String cannot be casted to voidエラーが発生します。


6
正しい正式な用語はExpressionStatementです。インスタンス作成式は、式またはステートメントが必要な場所の両方に表示される場合がありますが、Stringリテラルはステートメントコンテキストで使用できない単なるです。
ホルガー2015年

2
受け入れられた答えは正式には正しいかもしれませんが、これはより良い説明です
edc65 2015年

3
@ edc65:そのため、この回答も賛成されました。ルールの理由と非公式の直感的な説明は確かに役立つかもしれませんが、すべてのプログラマーはその背後に正式なルールがあることを認識しておく必要があり、正式なルールの結果が直感的に理解できない場合でも、正式なルールが勝ちます。たとえば()->x++、合法ですが()->(x++)、基本的にまったく同じことをしているのですが、そうではありません…
Holger 2015年

21

最初のケースは、「特別な」メソッド(コンストラクター)を呼び出しており、作成されたオブジェクトを実際に取得していないため、問題ありません。より明確にするために、ラムダにオプションの中括弧を入れます。

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

前者の場合、コンストラクターを実行していますが、作成されたオブジェクトを返していません。後者の場合、文字列値を返そうとしていますが、インターフェイスのメソッドBiConsumerはvoidを返すため、コンパイラエラーが発生します。


12

JLSはそれを指定します

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

それを詳しく見てみましょう、

あなたのでtakeBiConsumer方法がvoid型である、ラムダ受信手段new String("hi")のようなブロックとしてそれを解釈します

{
    new String("hi");
}

これはvoidで有効であるため、最初のケースはコンパイルされます。

ただし、ラムダがの場合、次の-> "hi"ようなブロック

{
    "hi";
}

Javaでは有効な構文ではありません。したがって、「こんにちは」と関係があるのは、それを返そうとすることだけです。

{
    return "hi";
}

これは無効で無効であり、エラーメッセージを説明します

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)以外の場所のプログラムで発生した場合、コンパイル時エラーになります。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.