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 )は、ターゲットタイプが選択されるまで意味を判別できないため、適用性テストでは無視されます。
上記の制限は、おそらく here と here からわかるように、 JEP 101 が完全には実装されていなかったという事実に関連しています。
JLSのどの部分がこのコンパイル時のあいまいさを指定しているのか(またはコンパイラのバグですか)正確に誰に教えてもらえますか?
おまけ:なぜこのように決定されたのですか?
Jdk1.8.0_40を使用すると、上記は正常にコンパイルおよび動作します
私はあなたが見つけたと思います コンパイラのこのバグ: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); }
正常に完了しないためですが、これはあなたの質問には当てはまりません。
このバグはすでにJDKバグシステムで報告されています: https://bugs.openjdk.Java.net/browse/JDK-8029718 。確認できるようにバグが修正されました。この修正により、この点でjavacが仕様と同期されます。現在、javacは暗黙のラムダを持つバージョンを正しく受け入れています。このアップデートを取得するには、 javac 8 repo のクローンを作成する必要があります。
修正が行うことは、ラムダ本体を分析し、それがvoidまたは値の互換性があるかどうかを判断することです。これを決定するには、すべてのreturnステートメントを分析する必要があります。上記ですでに参照されている仕様(15.27.2)から覚えておいてください。
つまり、ラムダボディの戻り値を分析することで、ラムダボディに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
これがお役に立てば幸いです。
メソッドとメソッド呼び出しがあるとしましょう
_void run(Function<Integer, Integer> f)
run(i->i)
_
合法的に追加できる方法は何ですか?
_void run(BiFunction<Integer, Integer, Integer> f)
void run(Supplier<Integer> f)
_
ここで、パラメーターアリティは異なります。具体的には、_i->
_の_i->i
_部分は、BiFunction
のapply(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)
はFunction
とIntUnaryOperator
の両方に有効ですが、両方の関数がこのラムダに一致するため、これは_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->
_のi
はInteger
でなければならないことがわかります。したがって、呼び出されるのはrun(Function)
でなければならないことがわかります。しかし、コンパイラーはこれを試みません。コンパイラが予期しないことを行うのはこれが初めてです。
なぜこれを行わないのですか?これは非常に特殊なケースであり、ここでタイプを推測するには、他の上記のケースでは見られなかったメカニズムが必要です。一般的なケースでは、タイプを正しく推測して正しい方法を選択できないためです。 。