Java8コンパレータ型推論によって非常に混乱している


84

特に静的メソッドの使用と、ラムダ式でparamタイプが必要かどうかに関して、Collections.sortとの違いを調べてきました。始める前に、たとえば問題を克服するためにメソッド参照を使用できることはわかっていますが、ここでのクエリは修正したいものではなく、答えが必要なものです。つまり、Javaコンパイラがこのように処理するのはなぜですか。 。list.sortComparatorSong::getTitle

これらは私の発見です。我々が持っていると仮定しArrayListたタイプのをSong追加いくつかの曲で、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())
        );

ここで、紛らわしい部分があります。p laylist1.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、compareingメソッド呼び出しの最初のラムダパラメーターから型を削除すると、comparingの呼び出しとtoの最初の呼び出しで構文エラーが表示されますがthenComparing、最後の呼び出しでは表示されませんthenComparing-上記で起こったこととほぼ逆です!他のすべての3例がでIE用、一方Objects.compareList.sortそしてCollections.sort私はその最初の削除するときSongのparamタイプを、それはすべてのコールのための構文エラーを示しています。

よろしくお願いします。

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

Eclipseでのソートエラー


すべてのテストで取得するすべてのコンパイルエラーメッセージを質問に含めると便利です。
エラン2014年

1
正直なところ、ソースコードを自分で実行することで、問題が何であるかを誰かが確認するのが最も簡単だと思います。
静けさ2014年

種類は何ですかt1t2におけるObjects.compare例は?私はそれらを推論しようとしていますが、コンパイラの型推論の上に型推論を重ねることは困難です。:-)
スチュアートマークス

1
また、どのコンパイラを使用していますか?
スチュアートマークス2014年

1
ここでは、2つの別々の問題が発生しています。回答者の1人は、メソッド参照を使用できると指摘しました。ラムダが「明示的に型付けされた」フレーバーと「暗黙的に型付けされた」フレーバーの両方で提供されるのと同様に、メソッド参照には「正確」(1つのオーバーロード)と「不正確」(複数のオーバーロード)のフレーバーがあります。正確なメソッドrefまたは明示的なラムダのいずれかを使用して、存在しない場合は追加のタイピング情報を提供できます。(明示的なタイプの目撃者とキャストも使用できますが、多くの場合、より大きなハンマーです。)
Brian Goetz 2014年

回答:


105

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

あなたの根本的な質問は、「チェーンを開始すると、なぜ機能しなくなるのか」ということのようです。その理由は、ラムダ式とジェネリックメソッド呼び出しは、メソッドパラメーターとして表示される場合はポリ式(タイプは状況依存)ですが、メソッドレシーバー式として表示される場合はそうではないためです。

あなたが言う時

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

のタイプ引数comparing()と引数タイプの両方を解決するのに十分なタイプ情報がありますp1comparing()コールは、署名からそのターゲット・タイプを取得し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>ます。

13
単に回避策/解決策を提供するのではなく、OPに実際に「コンパイラがこれを推測できない理由」に答えるための+1。
ジョフリー2014年

答えてくれてありがとうブライアン。しかし、私はまだ答えられていないものを見つけます。List.sortがCollections.sortとは異なる動作をするのはなぜですか。前者は最初のラムダにパラメーターの型を含めるだけで済みますが、後者は最後のラムダも必要です。たとえば、比較する場合などです。その後に5つのthenComparing呼び出しが続き、比較と最後のthenComparingに(Song p1)を入力する必要があります。また、私の元の投稿では、TreeSetの一番下の例が表示されます。ここでは、すべてのparamタイプを削除しましたが、thenComparingの最後の呼び出しは問題ありませんが、他の呼び出しは問題ありません。したがって、これは動作が異なります。
静けさ

3
@ user3780370まだEclipseコンパイラを使用していますか?私があなたの質問を正しく理解していれば、私はこの振る舞いを見ていません。(a)JDK 8のjavacで試してみて、(b)それでも失敗する場合は、コードを投稿できますか?
ブライアンゲッツ

@BrianGoetzこの提案をありがとう。javacを使用してコマンドウィンドウ内でコンパイルしたところ、あなたが言ったようにコンパイルされます。Eclipseの問題のようです。JDK8専用に構築されたEclipseLunaにはまだ更新していないので、修正されることを願っています。私は実際にEclipseで何が起こっているかを示すスクリーンショットを持っていますが、ここに投稿する方法がわかりません。
静けさ2014年

2
私はあなたが意味すると思いますComparator.<Song, String>comparing(...)
shmosel 2017

23

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

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

  1. 新しいJava8メソッド参照構文を使用する

     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)
                );
    

    編集

  3. コンパレータによって返されるタイプを強制します(入力タイプと比較キータイプの両方が必要であることに注意してください)

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

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

同じキャプチャタイプを実行する必要があるが、明らかにそうでListはないため、なぜより良い推論ジョブを実行しているのかCollectionわかりません。


なぜそれArrayListCollections解決策ではなくそれを知っているのですか(チェーンの最初の呼び出しにSongパラメーターがある場合)?
Sotirios Delimanolis 2014年

4
返信ありがとうございます。ただし、私の投稿を読むと、次のように表示されます。「始める前に、Song :: getTitleなどのメソッド参照を使用して問題を解決できることはわかっていますが、ここでのクエリはそれほど多くありません。修正したいのですが、答えが欲しいのです。つまり、Javaコンパイラがこのように処理するのはなぜですか。」
静けさ2014年

ラムダ式を使用するときにコンパイラがそのように動作する理由についての回答が必要です。比較(s-> s.getArtist())を受け入れますが、たとえば.thenComparing(s-> s.getDuration())をチェーンすると、両方の呼び出しの構文エラーが発生します。次に明示的な型を追加すると、比較呼び出し、たとえばcompareing((Song s)-> s.getArtist())の場合、これによりその問題が修正され、List.sortおよびTreeSetの場合、追加のパラメータータイプを追加することなく、以降のすべてのコンパイルエラーも解決されます。 Collections.sort&Objects.compareの例最後のthenComparingはまだ失敗します
静けさ

1

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

最初の比較関数の変数を明示的にキャストしてから、準備を進めてください。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

playlist1.sort(...) コンパレータに「波及」するplaylist1の宣言から、型変数EのSongの境界を作成します。

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

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


こんにちは、はい、コレクションを追加するという点で正しいです。<Song>は、最後のthenComparing呼び出しのエラーを取り除きます
Tranquility
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.