驚くべきことに、次のコードは正常にコンパイルされています。
Consumer<String> p = ""::equals;
これも:
p = s -> "".equals(s);
しかし、これは期待どおりboolean cannot be converted to void
エラーで失敗します:
p = s -> true;
括弧を使用した2番目の例の変更も失敗します。
p = s -> ("".equals(s));
Javaコンパイラのバグですか、それとも私が知らない型推論規則がありますか?
最初に、_Consumer<String>
_が実際に何であるかを見る価値があります。 ドキュメントから :
単一の入力引数を受け入れ、結果を返さない操作を表します。他のほとんどの機能的インターフェースとは異なり、Consumerは副作用を介して動作することが期待されています。
したがって、文字列を受け入れて何も返さない関数です。
_Consumer<String> p = ""::equals;
_
equals
は文字列(および実際には任意のオブジェクト)を取得できるため、正常にコンパイルされます。 equalsの結果は無視されます。*
_p = s -> "".equals(s);
_
これはまったく同じですが、構文が異なります。 return
は値を返すべきではないため、コンパイラは暗黙のConsumer
を追加しないことを知っています。 wouldラムダが_Function<String, Boolean>
_であった場合、暗黙のreturn
を追加します。
_p = s -> true;
_
これは文字列(s
)を取りますが、true
は式であり、ステートメントではないため、同じ方法で結果を無視することはできません。コンパイラhas toは、式が単独では存在できないため、暗黙的なreturn
を追加します。したがって、このdoesには戻り値があります:ブール値。したがって、それはConsumer
ではありません。**
_p = s -> ("".equals(s));
_
繰り返しますが、これはexpressionであり、ステートメントではありません。ラムダをしばらく無視すると、System.out.println("Hello");
という行が括弧で囲むと同様にコンパイルに失敗します。
* From 仕様 :
ラムダの本体がステートメント式(つまり、ステートメントとして単独で使用できる式)である場合、それはvoidを生成する関数型と互換性があります。結果はすべて破棄されます。
ラムダ式は、...ラムダ本体がステートメント式( §14.8 )またはvoid互換ブロックのいずれかである場合、[void-produce]関数タイプと一致します。
他の答えはラムダに焦点を当てることで説明を複雑にしていると思いますが、この場合のラムダの動作は手動で実装されたメソッドの動作に似ています。これはコンパイルします:
_new Consumer<String>() {
@Override
public void accept(final String s) {
"".equals(s);
}
}
_
一方、これはしません:
_new Consumer<String>() {
@Override
public void accept(final String s) {
true;
}
}
_
"".equals(s)
はステートメントですが、true
はそうではないためです。 voidを返す関数型インターフェースのラムダ式にはステートメントが必要なので、メソッドの本体と同じルールに従います。
一般に、ラムダ本体は正確にメソッド本体と同じ規則に従いません-特に、本体が式であるラムダが値を返すメソッドを実装する場合、暗黙のreturn
。したがって、たとえば_x -> true
_は_Function<Object, Boolean>
_の有効な実装になりますが、_true;
_は有効なメソッド本体ではありません。しかし、この特定のケースでは、機能的インターフェースとメソッド本体は一致しています。
s -> "".equals(s)
そして
s -> true
同じ関数記述子に依存しないでください。
s -> "".equals(s)
は、String->void
またはString->boolean
関数記述子のいずれかを参照できます。s -> true
は、String->boolean
関数記述子のみを参照します。
どうして ?
s -> "".equals(s)
と書くと、ラムダの本体:"".equals(s)
値を生成するステートメント。void
またはboolean
を返す可能性があると見なします。だから書く:
Function<String, Boolean> function = s -> "".equals(s);
Consumer<String> consumer = s -> "".equals(s);
有効です。
ラムダ本体をConsumer<String>
宣言変数に割り当てると、記述子String->void
が使用されます。
もちろん、このコードはあまり意味がありません(等価性をチェックし、結果を使用しません)が、コンパイラーは気にしません。
ステートメントを書くときも同じです:myObject.getMyProperty()
where getMyProperty()
はboolean
値を返しますが、その結果を保存しません。
s -> true
と書くと、ラムダの本体:true
単一の式.boolean
を返すと見なします。String->boolean
のみを使用できます。さて、コンパイルされないコードに戻ってください。
あなたは何をしようとしているのですか ?
Consumer<String> p = s -> true;
できません。関数記述子Consumer<String>
を使用する変数に、String->void
関数記述子を持つラムダ本体を割り当てます。一致しません!