negate()がPredicateへの明示的なキャストを必要とするのはなぜですか?


8

名前のリストがあります。3行目では、ラムダ式の結果をにキャストする必要がありましたPredicate<String>。私が読んでいる本は、コンパイラが一致する機能的インターフェースが何であるかを決定するのを助けるためにキャストが必要であることを説明しています。

ただし、を呼び出さないため、次の行ではそのようなキャストは必要ありませんnegate()。これはどのように違いますか?negate()ここでが返されることを理解していますPredicate<String>が、前のラムダ式は同じことをしませんか?

List<String> names = new ArrayList<>();
//add various names here
names.removeIf(((Predicate<String>) str -> str.length() <= 5).negate()); //cast required
names.removeIf(((str -> str.length() <= 5))); //compiles without cast

1
...キャストは、コンパイラが一致する機能インターフェイスを判断するために必要です。とにかく答えます。次の行が必要としない理由は、推論がはるかに単純であり、推論されたインターフェイスでのメソッド呼び出しが後に続かないためです。私はそれがこのような場合には推論についてほとんどだと、ジェネリックを含むラムダ関数を使用して学んだと明示的な宣言は、以下のような非常に安全で、あるPredicate<String> predicate = str -> str.length() <= 5; names.removeIf(predicate.negate()); names.removeIf(predicate);
ナマン

@Nathan negate()の呼び出しでさらに多くのコンテキストが提供され、明示的なキャストの必要性がさらに低下するべきではありませんか?結局、ラムダ式はPredicateではなくFunctionを参照している可能性がありますが、その後のnegate()の呼び出しにより、あいまいさの余地がなくなります。
Kマン

しかしnegate()、式の一部であるラムダ属性の型を推測できず、さらに式がを意味することを確立できない場合、何が必要になるでしょうかPredicate<String>
ナマン

negate()が必要だと言っているのではありません。私の最後の行はそれなしで明らかに機能しました。ただし、negate()を呼び出すと、関数型インターフェースのタイプに関してコンパイラーが突然混乱することはわかりません。
Kマン

回答:


8

あなたが電話をしnegate()からといって、それだけではありません。あなたのバージョンに非常に近いがコンパイルできるこのバージョンを見てください:

Predicate<String> predicate = str -> str.length() <= 5;
names.removeIf(predicate.negate());

これとあなたのバージョンの違いは?ラムダ式がどのように型(「ターゲット型」)を取得するかについてです。

これは何と思いますか?

(str -> str.length() <= 5).negate()?

あなたの現在の答えは「それが呼び出すことであるnegate()Predicate<String>式で与えられますstr -> str.length() <= 5」。正しい?しかし、それはあなたがそうするつもりだったからです。コンパイラはそれを知りません。どうして?何でもいいから。上記の質問に対する私自身の答えは、「negate他の機能的なインターフェイスタイプを呼び出します...(はい、例は少し奇妙です)

interface SentenceExpression {
    boolean checkGrammar();
    default SentenceExpression negate() {
        return ArtificialIntelligence.contradict(explainSentence());
    };
}

同じラムダ式を使用できますが、ではなくであることをnames.removeIf((str -> str.length() <= 5).negate());意味 str -> str.length() <= 5します。SentenceExpressionPredicate<String>

説明:(str -> str.length() <= 5).negate()ことはありません。これが、上記の機能インターフェイスを含め、何でもよいと私が言った理由です。str -> str.length() <= 5Predicate<String>

Javaに戻る...これがラムダ式に「ターゲットタイプ」の概念がある理由です。ラムダ式は、特定の関数型インターフェースタイプとしてコンパイラがラムダ式を理解するためのメカニズムを定義します(つまり、コンパイラが表現はPredicate<String>むしろそれ以外のSentenceExpression何かである可能性があります)。あなたはそれが役に立つを通して読むことを見つけることが、Javaのラムダターゲット型とターゲット型文脈によって意味される何?およびJava 8:ターゲットの型付け

ターゲットタイプが推測されるコンテキストの1つ(それらの投稿の回答を読んだ場合)は呼び出しコンテキストです。ここで、ラムダ式を関数型インターフェイスタイプのパラメーターの引数として渡します。これは、names.removeIf(((str -> str.length() <= 5)));:これは、を受け取るメソッドの引数として指定されたラムダ式Predicate<String>です。これは、コンパイルされていないステートメントには適用されません。

つまり、言い換えれば...

names.removeIf(str -> str.length() <= 5);引数の型がラムダ式の型が何であることが期待されるものであるかを明確に定義する場所でラムダ式を使用します(つまり、ターゲット型str -> str.length() <= 5は明らかにですPredicate<String>)。

ただし、(str -> str.length() <= 5).negate()はラムダ式ではなく、たまたまラムダ式を使用する式です。つまりstr -> str.length() <= 5、この場合(最後のステートメントの場合のように)ラムダ式のターゲットタイプを決定する呼び出しコンテキストにはありません。はい、コンパイラはがremoveIf必要であることを知っていますPredicate<String>。また、メソッドに渡される式全体Predicate<String>がでなければならないことは確かですが、引数式のラムダ式がPredicate<String>(であっても)であるとは想定していません。negate()それを呼び出すことによる述語として。それはラムダ式と互換性のあるものである可能性があります)。

そのため、明示的なキャストでラムダを入力する必要があります(または、最初に示した反例のように、それ以外の場合)。


names.removeIf(((str-> str.length()<= 5))); 問題なくコンパイルできます。それでも、Predicateを返すnegate()を呼び出すと、コンパイラーが式全体がPredicateを参照していることを突然疑わせることができません。
Kマン

@KManラムダ式がどのように型付けされるかについての詳細が必要だと思います。リンクを編集して追加します。
ernest_k

私はすでにラムダ式についてかなり読んでいます。式全体がラムダ式であるかどうかは関係ありませんね。上記のコードでnegate()がPredicate <String>を返さないのですか?私は間違っているかもしれませんが、私はそうは思いません。
Kマン

1
@KMan 上記のコードでnegate()はPredicate <String>を返しませんか?..いいえ、それは、この回答で説明されているようにそれ自体が使用されているためにコンパイラーがそもそも推論できなかったものが呼び出されるまでPredicate<String>はありませんnegate
ナマン

@KMan私は編集しました-答えをもう一度読む必要があると思います。要するに、ナマンが言っているように、それはあなたが意図しているものであったとしても、(str -> str.length() <= 5).negate()はにならないという理解に要約されますstr -> str.length() <= 5Predicate<String>
ernest_k

4

なぜこれがそんなに紛らわしいのか分かりません。これは、IMOという2つの理由で説明できます。

  • ラムダ式はポリ式です。

これが何を意味するのか、そしてそのJLS周りの言葉が何であるのかを理解させてください。しかし、本質的にこれらはジェネリックのようなものです:

static class Me<T> {
    T t...
}

Tここのタイプは何ですか?まあ、それは異なります。もしあなたがそうするなら :

Me<Integer> me = new Me<>(); // it's Integer
Me<String>  m2 = new Me<>(); // it's String

ポリ式は、使用される場所のコンテキストに依存すると言われています。ラムダ式は同じです。ここでラムダ式を分離してみましょう:

(String str) -> str.length() <= 5

あなたがそれを見るとき、これは何ですか?まあそれはPredicate<String>?しかし、AかもしれませんFunction<String, Boolean>か?またはかもしれませんMyTransformer<String, Boolean>、ここで:

 interface MyTransformer<String, Boolean> {
     Boolean transform(String in){
         // do something here with "in"
     } 
 } 

選択肢は無限です。

  • 理論的に.negate()は、直接呼び出すこともできます。

上記の10_000マイルから、あなたは正しいです。あなたはそれstr -> str.length() <= 5を、removeIfだけを受け入れるメソッドに提供していPredicateます。これ以上removeIfメソッドはないので、コンパイラーはそれを指定したときに「正しいことを行う」ことができるはずです(str -> str.length() <= 5).negate()

では、なぜこれが機能しないのですか?コメントから始めましょう:

negate()の呼び出しでさらに多くのコンテキストが提供され、明示的なキャストの必要性がさらに低下するのではないですか

これが主な問題の始まりであるようjavacです。これは単に動作する方法ではありません。全体をとることはできません。str -> str.length() <= 5).negate()これはPredicate<String>(それをの引数として使用しているためremoveIf)であることを伝えてから、パーツをさらに分解して、.negate()それがaかどうかを確認しPredicate<String>ます。javac逆に振る舞うので、呼び出すことが合法かどうかを判断できるように、ターゲットを知る必要がnegateあります。

また、一般的に、ポリ式式を明確に区別する必要があります。str -> str.length() <= 5).negate()は式、str -> str.length() <= 5はポリ式です。

物事が別様に行われ、これが可能である言語が、javac単にそのタイプではない場合があります。


1

次の例では

      names.removeIf(str -> str.length() <= 5); //compiles without cast

式はtrueを返します。次の例でキャストなしの場合true、メソッドについて何も知らないnegate()

一方、

   names.removeIf(((Predicate<String>) str -> str.length() <= 5).negate()); //cast required

式がキャストさPredicate<String>れて、コンパイラーにメソッドの検索場所を通知しますnegate。次にnegate()、実際に次の方法で評価を行う方法です。

   (s)->!test(s) where s is the string argument

次のように、キャストなしでも同じ結果が得られることに注意してください。

    names.removeIf(str->!str.length <= 5) 
      // or
    name.removeIf(str->str.length > 5)

1

物事に深く入り込むことなく、肝心なことstr -> str.length() <= 5は、ラムダは必ずしもユージーンが説明したPredicate<String>ようなaではないということです。

つまり、はのnegate()メンバー関数でPredicate<T>あり、コンパイラーはへの呼び出しを使用しnegate()て、ラムダが解釈される型を推測するのに役立ちません。試したとしても、複数の可能なクラスにnegate()関数がある場合、どちらを選択するかわからないため、問題が発生する可能性があります。

あなたがコンパイラだと想像してみてください。

  • ほらstr -> str.length() <= 5。文字列を取りブール値を返すのはラムダであることがわかりますが、それがどの型を表すのかは明確にはわかりません。
  • 次にメンバー参照.negate()が表示されますが、「。」の左側にある式のタイプがわからないためです。呼び出しを使用してタイプを推論するために否定することはできないため、エラーを報告する必要があります。

negateが静的関数として実装されていれば、問題はありませんでした。例えば:

public class PredicateUtils {
    public static <T> Predicate<T> negate(Predicate<T> p) {
        return p.negate();
}

あなたが書くことを可能にするでしょう

names.removeIf(PredicateUtils.negate(str -> str.length() <= 5)); //compiles without cast

否定関数の選択は明確であるため、コンパイラーはそれstr -> str.length() <= 5をaとして解釈する必要があることを認識しPredicate<T>、型を適切に強制できます。

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

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