web-dev-qa-db-ja.com

ラムダ式とメソッドのオーバーロードの疑問

OK、メソッドのオーバーロードは悪いことです™。これが解決されたので、実際にwantで次のようなメソッドをオーバーロードするとします。

static void run(Consumer<Integer> consumer) {
    System.out.println("consumer");
}

static void run(Function<Integer, Integer> function) {
    System.out.println("function");
}

Java 7では、明確な匿名クラスを引数として簡単に呼び出すことができます。

run(new Consumer<Integer>() {
    public void accept(Integer integer) {}
});

run(new Function<Integer, Integer>() {
    public Integer apply(Integer o) { return 1; }
});

Java 8では、もちろんラムダ式でこれらのメソッドを呼び出したいのですが、できます!

// Consumer
run((Integer i) -> {});

// Function
run((Integer i) -> 1);

コンパイラーIntegerを推論できるはずですので、Integerを残さないのはなぜですか?

// Consumer
run(i -> {});

// Function
run(i -> 1);

しかし、これはコンパイルされません。コンパイラ(javac、jdk1.8.0_05)はそれが好きではありません。

Test.Java:63: error: reference to run is ambiguous
        run(i -> {});
        ^
  both method run(Consumer<Integer>) in Test and 
       method run(Function<Integer,Integer>) in Test match

私には、直感的に、これは意味がありません。 JLS§15.27 で説明されているように、戻り値を生成するラムダ式( "値互換")とvoid( "空白互換")を生成するラムダ式の間には、あいまいさはまったくありません。 =。

しかしもちろん、JLSは深くて複雑であり、20年間の下位互換性の歴史を継承しており、次のような新しいものがあります。

を暗黙的に入力した特定の引数式ラムダ式§15.27.1 =)または不正確なメソッド参照( §15.13.1 )は、ターゲットタイプが選択されるまで意味を判別できないため、適用性テストでは無視されます。

JLS§15.12.2から

上記の制限は、おそらく herehere からわかるように、 JEP 101 が完全には実装されていなかったという事実に関連しています。

質問:

JLSのどの部分がこのコンパイル時のあいまいさを指定しているのか(またはコンパイラのバグですか)正確に誰に教えてもらえますか?

おまけ:なぜこのように決定されたのですか?

更新:

Jdk1.8.0_40を使用すると、上記は正常にコンパイルおよび動作します

48
Lukas Eder

私はあなたが見つけたと思います コンパイラのこのバグ:JDK-8029718またはEclipseのこれに似たもの:434642 )。

と比較 JLS§15.12.2.1。潜在的に適用可能なメソッドを特定する

  • ラムダ式(§15.27)は、次の条件がすべて当てはまる場合、機能インターフェースタイプ(§9.8)と互換性がある可能性があります。

    • ターゲット型の関数型のアリティは、ラムダ式のアリティと同じです。

    • ターゲット型の関数型にvoid戻りがある場合、ラムダ本体はステートメント式(§14.8)またはvoid互換ブロック(§15.27.2)のいずれかです。

    • ターゲット型の関数型に(非void)戻り型がある場合、ラムダ本体は式または値互換ブロックです(§15.27.2)。

void互換ブロック」と「値互換ブロック」の明確な違いに注意してください。特定のケースではブロックが両方になる可能性がありますが、セクション §15.27.2。Lambda Body は、() -> {}のような式が「void互換ブロック」であることを明確に述べています。値を返さずに正常に完了するため。また、i -> {}も「void互換ブロック」であることは明らかです。

また、上記のセクションによれば、ラムダと値互換性のないブロックの組み合わせ、およびターゲット型と(非void)戻り型の組み合わせは、メソッドのオーバーロード解決の潜在的な候補ではありません。だからあなたの直感は正しいです、ここに曖昧さはありません。

あいまいなブロックの例は

() -> { throw new RuntimeException(); }
() -> { while (true); }

正常に完了しないためですが、これはあなたの質問には当てはまりません。

19
Holger

このバグはすでにJDKバグシステムで報告されています: https://bugs.openjdk.Java.net/browse/JDK-8029718 。確認できるようにバグが修正されました。この修正により、この点でjavacが仕様と同期されます。現在、javacは暗黙のラムダを持つバージョンを正しく受け入れています。このアップデートを取得するには、 javac 8 repo のクローンを作成する必要があります。

修正が行うことは、ラムダ本体を分析し、それがvoidまたは値の互換性があるかどうかを判断することです。これを決定するには、すべてのreturnステートメントを分析する必要があります。上記ですでに参照されている仕様(15.27.2)から覚えておいてください。

  • ブロック内のすべてのreturnステートメントの形式がreturnである場合、ブロックラムダ本体はvoid互換です。
  • ブロックラムダ本体は、正常に完了できず( 14.21 )、ブロック内のすべてのreturnステートメントの形式がreturn Expressionである場合、値互換です。

つまり、ラムダボディの戻り値を分析することで、ラムダボディにvoid互換性があるかどうかを確認できますが、値互換性があるかどうかを判断するには、フロー分析を実行して、正常に完了できるかどうかを判断する必要があります( 14.21 )。

この修正により、本体がvoidでも値の互換性でもない場合、たとえば次のコードをコンパイルした場合に、新しいコンパイラエラーが発生します。

class Test {
    interface I {
        String f(String x);
    }

    static void foo(I i) {}

    void m() {
        foo((x) -> {
            if (x == null) {
                return;
            } else {
                return x;
            }
        });
    }
}

コンパイラはこの出力を提供します:

Test.Java:9: error: lambda body is neither value nor void compatible
    foo((x) -> {
        ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

これがお役に立てば幸いです。

3
Vicente Romero

メソッドとメソッド呼び出しがあるとしましょう

_void run(Function<Integer, Integer> f)

run(i->i)
_

合法的に追加できる方法は何ですか?

_void run(BiFunction<Integer, Integer, Integer> f)
void run(Supplier<Integer> f)
_

ここで、パラメーターアリティは異なります。具体的には、_i->_の_i->i_部分は、BiFunctionapply(T,U)のパラメーター、またはget()のパラメーターに適合しません。 Supplierにあります。したがって、ここで考えられる曖昧さは、型ではなく、戻り値ではなく、パラメータアリティによって定義されます。


追加できないメソッドは何ですか?

_void run(Function<Integer, String> f)
_

これにより、run(..) and run(..) have the same erasureとしてコンパイラエラーが発生します。したがって、JVMは同じ名前と引数タイプの2つの関数をサポートできないため、これをコンパイルすることはできません。したがって、Java型システムに既存のルールがあるため、明示的に許可されていないため、このタイプのシナリオでは曖昧さを解決する必要はありません。

したがって、パラメーターアリティが1のその他の関数型が残ります。

_void run(IntUnaryOperator f)
_

ここでrun(i->i)FunctionIntUnaryOperatorの両方に有効ですが、両方の関数がこのラムダに一致するため、これは_reference to run is ambiguous_のためにコンパイルを拒否します。確かにそうであり、ここでエラーが予想されます。

_interface X { void thing();}
interface Y { String thing();}

void run(Function<Y,String> f)
void run(Consumer<X> f)
run(i->i.thing())
_

ここでも、あいまいさのため、これはコンパイルに失敗します。このラムダのiのタイプを知らなければ、i.thing()のタイプを知ることは不可能です。したがって、これはあいまいであり、正しくコンパイルできないことに同意します。


あなたの例では:

_void run(Consumer<Integer> f)
void run(Function<Integer,Integer> f)
run(i->i)
_

ここでは、両方の関数型が単一のIntegerパラメーターを持っていることがわかっているため、_i->_のiIntegerでなければならないことがわかります。したがって、呼び出されるのはrun(Function)でなければならないことがわかります。しかし、コンパイラーはこれを試みません。コンパイラが予期しないことを行うのはこれが初めてです。

なぜこれを行わないのですか?これは非常に特殊なケースであり、ここでタイプを推測するには、他の上記のケースでは見られなかったメカニズムが必要です。一般的なケースでは、タイプを正しく推測して正しい方法を選択できないためです。 。

0
ggovan