web-dev-qa-db-ja.com

Java 8コンパレーター型推論による混乱

Collections.sortlist.sortの違い、特にComparator静的メソッドの使用と、ラムダ式でparam型が必要かどうかを見てきました。始める前に、メソッド参照を使用できることを知っています。 Song::getTitleは私の問題を克服しますが、ここでのクエリは修正したいものではなく、答えが欲しいものです。つまり、なぜJavaコンパイラがこれを処理するのですか仕方。

これらは私の発見です。タイプArrayListSongがあり、いくつかの曲が追加されているとします。3つの標準getメソッドがあります。

    ArrayList<Song> playlist1 = new ArrayList<Song>();

    //add some new Song objects
    playlist.addSong( new Song("Only Girl (In The World)", 235, "Rhianna") );
    playlist.addSong( new Song("Thinking of Me", 206, "Olly Murs") );
    playlist.addSong( new Song("Raise Your Glass", 202,"P!nk") );

正常に機能する両方のタイプのソートメソッドの呼び出しを次に示しますが、問題はありません。

Collections.sort(playlist1, 
            Comparator.comparing(p1 -> p1.getTitle()));

playlist1.sort(
            Comparator.comparing(p1 -> p1.getTitle()));

thenComparingのチェーンを開始するとすぐに、次のことが起こります。

Collections.sort(playlist1,
            Comparator.comparing(p1 -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

playlist1.sort(
        Comparator.comparing(p1 -> p1.getTitle())
        .thenComparing(p1 -> p1.getDuration())
        .thenComparing(p1 -> p1.getArtist())
        );

つまり、p1のタイプがわからなくなったための構文エラーです。これを修正するために、(比較の)最初のパラメーターにタイプSongを追加します。

Collections.sort(playlist1,
            Comparator.comparing((Song p1) -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

playlist1.sort(
        Comparator.comparing((Song p1) -> p1.getTitle())
        .thenComparing(p1 -> p1.getDuration())
        .thenComparing(p1 -> p1.getArtist())
        );

次に、混乱の部分があります。 playlist1.sort、つまりリストの場合、これは、次のthenComparing呼び出しの両方について、すべてのコンパイルエラーを解決します。ただし、Collections.sortの場合、最初の問題は解決しますが、最後の問題は解決しません。テストしたthenComparingへの追加の呼び出しがいくつかあり、パラメーターに(Song p1)を指定しない限り、最後の呼び出しに対して常にエラーが表示されます。

次に、TreeSetを作成し、Objects.compareを使用して、これをさらにテストしました。

int x = Objects.compare(t1, t2, 
                Comparator.comparing((Song p1) -> p1.getTitle())
                .thenComparing(p1 -> p1.getDuration())
                .thenComparing(p1 -> p1.getArtist())
                );


    Set<Song> set = new TreeSet<Song>(
            Comparator.comparing((Song p1) -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

同じことが起こります。TreeSetの場合、コンパイルエラーはありませんが、Objects.compareの場合、thenComparingの最後の呼び出しではエラーが表示されます。

なぜこれが起こっているのか、また単に比較メソッドを呼び出すときに_(Song p1)を使用する必要がない理由を説明できますか(さらにthenComparing呼び出しなしで)。

同じトピックに関するもう1つのクエリは、TreeSetに対してこれを行う場合です。

Set<Song> set = new TreeSet<Song>(
            Comparator.comparing(p1 -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

つまり、比較メソッド呼び出しの最初のラムダパラメータからSong型を削除すると、比較呼び出しとthenComparingの最初の呼び出しで構文エラーが表示されますが、thenComparingの最後の呼び出しでは表示されません-上記とほぼ逆です!一方、他の3つのすべての例、つまりObjects.compareList.sortおよびCollections.sortでは、最初のSong paramタイプを削除すると、すべての呼び出しの構文エラーが表示されます。

事前に感謝します。

Eclipse Kepler SR2で受け取っていたエラーのスクリーンショットを含めるように編集しました。これは、コマンドラインでJDK8 Javaコンパイラーを使用してコンパイルするとOKにコンパイルされるため、Eclipse固有のものです。

Sort errors in Eclipse

71
Tranquility

まず、エラーの原因となるすべての例は、リファレンス実装(JDK 8のjavac)で問題なくコンパイルされます。これらはIntelliJでも正常に動作するため、表示されるエラーはEclipse固有のものです。

あなたの根底にある質問は、「連鎖を開始したときになぜ機能しなくなるのか」ということです。その理由は、ラムダ式と一般的なメソッド呼び出しがpoly式(それらのタイプはコンテキストに依存する)がメソッドパラメーターとして表示される場合、メソッドレシーバー式として表示される場合ではないためです。

あなたが言う時

_Collections.sort(playlist1, comparing(p1 -> p1.getTitle()));
_

comparing()の型引数と引数型_p1_の両方について解決するのに十分な型情報があります。 comparing()呼び出しは_Collections.sort_のシグネチャからターゲットタイプを取得するため、comparing()は_Comparator<Song>_を返す必要があることがわかっているため、_p1_ Songでなければなりません。

しかし、連鎖を開始するとき:

_Collections.sort(playlist1,
                 comparing(p1 -> p1.getTitle())
                     .thenComparing(p1 -> p1.getDuration())
                     .thenComparing(p1 -> p1.getArtist()));
_

問題が発生しました。複合式comparing(...).thenComparing(...)のターゲットタイプは_Comparator<Song>_ですが、チェーンのレシーバー式comparing(p -> p.getTitle())は汎用メソッド呼び出しであるため、他の引数から型パラメータを推測するのではなく、私たちは運が悪いのです。この式の型がわからないため、thenComparingメソッドなどがあることはわかりません。

これを修正するにはいくつかの方法がありますが、すべての方法では、チェーン内の初期オブジェクトを適切に入力できるように、より多くの型情報を注入します。ここでは、大まかな順序で、望ましさを減らし、侵入性を高めています。

  • _Song::getTitle_のような正確なメソッド参照(オーバーロードのないもの)を使用します。これにより、comparing()呼び出しの型変数を推測するのに十分な型情報が得られるため、型が与えられるため、チェーンをたどります。
  • 明示的なラムダを使用します(例で行ったように)。
  • comparing()呼び出しのタイプ監視を提供します:Comparator.<Song, String>comparing(...)
  • レシーバー式を_Comparator<Song>_にキャストすることにより、明示的なターゲットタイプにキャストを指定します。
84
Brian Goetz

問題は型推論です。最初の比較に(Song s)を追加しないと、comparator.comparingは入力のタイプを知らないため、デフォルトでObjectになります。

この問題は、次の3つの方法のいずれかで修正できます。

  1. 新しいJava 8メソッド参照構文を使用

     Collections.sort(playlist,
                Comparator.comparing(Song::getTitle)
                .thenComparing(Song::getDuration)
                .thenComparing(Song::getArtist)
                );
    
  2. 各比較ステップをローカル参照に引き出します

      Comparator<Song> byName = (s1, s2) -> s1.getArtist().compareTo(s2.getArtist());
    
      Comparator<Song> byDuration = (s1, s2) -> Integer.compare(s1.getDuration(), s2.getDuration());
    
        Collections.sort(playlist,
                byName
                .thenComparing(byDuration)
                );
    

    [〜#〜] edit [〜#〜]

  3. コンパレータから返される型を強制する(入力型と比較キー型の両方が必要なことに注意してください)

    sort(
      Comparator.<Song, String>comparing((s) -> s.getTitle())
                .thenComparing(p1 -> p1.getDuration())
                .thenComparing(p1 -> p1.getArtist())
                );
    

「最後の」thenComparing構文エラーは誤解を招くと思います。実際にはチェーン全体の型の問題であり、コンパイラがチェーンの終わりを構文エラーとしてマークしているだけです。これは、最終的な戻り値の型が一致しないときだからです。

ListCollectionよりも優れた推論ジョブを実行している理由はわかりませんが、それは同じキャプチャタイプを実行するはずですが、明らかにそうではないからです。

21
dkatzel

このコンパイル時エラーに対処する別の方法:

最初の比較関数の変数を明示的にキャストしてから、始めましょう。 org.bson.Documentsオブジェクトのリストをソートしました。サンプルコードをご覧ください

Comparator<Document> comparator = Comparator.comparing((Document hist) -> (String) hist.get("orderLineStatus"), reverseOrder())
                       .thenComparing(hist -> (Date) hist.get("promisedShipDate"))
                       .thenComparing(hist -> (Date) hist.get("lastShipDate"));
list = list.stream().sorted(comparator).collect(Collectors.toList());
0
Rajni Gangwar

playlist1.sort(...)は、playlist1の宣言から型変数EのSongのバウンドを作成し、コンパレーターに「リップル」します。

Collections.sort(...)では、そのような境界はなく、最初のコンパレーターの型からの推論は、コンパイラーが残りを推論するのに十分ではありません。

Collections.<Song>sort(...)から「正しい」動作が得られると思いますが、テストするためのJava 8インストールはありません。

0
amalloy