Collections.sort
とlist.sort
の違い、特にComparator
静的メソッドの使用と、ラムダ式でparam型が必要かどうかを見てきました。始める前に、メソッド参照を使用できることを知っています。 Song::getTitle
は私の問題を克服しますが、ここでのクエリは修正したいものではなく、答えが欲しいものです。つまり、なぜJavaコンパイラがこれを処理するのですか仕方。
これらは私の発見です。タイプ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())
);
次に、混乱の部分があります。 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.compare
、List.sort
およびCollections.sort
では、最初のSong
paramタイプを削除すると、すべての呼び出しの構文エラーが表示されます。
事前に感謝します。
Eclipse Kepler SR2で受け取っていたエラーのスクリーンショットを含めるように編集しました。これは、コマンドラインでJDK8 Javaコンパイラーを使用してコンパイルするとOKにコンパイルされるため、Eclipse固有のものです。
まず、エラーの原因となるすべての例は、リファレンス実装(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>
_にキャストすることにより、明示的なターゲットタイプにキャストを指定します。問題は型推論です。最初の比較に(Song s)
を追加しないと、comparator.comparing
は入力のタイプを知らないため、デフォルトでObjectになります。
この問題は、次の3つの方法のいずれかで修正できます。
新しいJava 8メソッド参照構文を使用
Collections.sort(playlist,
Comparator.comparing(Song::getTitle)
.thenComparing(Song::getDuration)
.thenComparing(Song::getArtist)
);
各比較ステップをローカル参照に引き出します
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 [〜#〜]
コンパレータから返される型を強制する(入力型と比較キー型の両方が必要なことに注意してください)
sort(
Comparator.<Song, String>comparing((s) -> s.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
「最後の」thenComparing
構文エラーは誤解を招くと思います。実際にはチェーン全体の型の問題であり、コンパイラがチェーンの終わりを構文エラーとしてマークしているだけです。これは、最終的な戻り値の型が一致しないときだからです。
List
がCollection
よりも優れた推論ジョブを実行している理由はわかりませんが、それは同じキャプチャタイプを実行するはずですが、明らかにそうではないからです。
このコンパイル時エラーに対処する別の方法:
最初の比較関数の変数を明示的にキャストしてから、始めましょう。 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());
playlist1.sort(...)
は、playlist1の宣言から型変数EのSongのバウンドを作成し、コンパレーターに「リップル」します。
Collections.sort(...)
では、そのような境界はなく、最初のコンパレーターの型からの推論は、コンパイラーが残りを推論するのに十分ではありません。
Collections.<Song>sort(...)
から「正しい」動作が得られると思いますが、テストするためのJava 8インストールはありません。