Javaのラムダ式でソートする方法まとめ!Collections.sortとStream.sortedの違いと使い分け
生徒
「先生、Javaのリストを並び替えるときってCollections.sortとstream().sorted()のどっちを使えばいいんですか?」
先生
「いい質問だね。それぞれ特徴があって、用途によって使い分けるのがポイントなんだ。」
生徒
「性能とか結果にも違いがあるんですか?」
先生
「あるよ。それじゃあJavaのラムダ式を使ったソート処理について、基本からしっかり見ていこう。」
1. Javaでリストをソートする基本:Collections.sort
Javaでリストを昇順や降順に並び替える方法として、昔からよく使われているのがCollections.sort()です。Listを直接並び替える処理であり、リストそのものを変更します(破壊的操作)。
例えば、文字列のリストをアルファベット順にソートしたいときは以下のように書きます。
import java.util.*;
public class SortExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Tanaka", "Yamada", "Sato");
Collections.sort(names);
System.out.println(names);
}
}
[Sato, Tanaka, Yamada]
このように、Collections.sortは破壊的(元のリストを変更)である点に注意が必要です。
2. ラムダ式とComparatorで自由にソート条件を指定
Java8以降では、Comparatorにラムダ式を使うことで、より柔軟なソートが可能になります。例えば名前の長さでソートしたい場合、次のように書きます。
Collections.sort(names, (a, b) -> Integer.compare(a.length(), b.length()));
このように、Javaのラムダ式を使えば、比較ロジックを簡潔に記述でき、複雑な条件でも読みやすくなります。
3. Stream.sortedの特徴と非破壊的ソート
一方、stream().sorted()は、JavaのStream APIのメソッドで、元のリストを変更せず、新しいソート済みのストリームを生成するのが特徴です。これを使えば、元のデータを保持したまま、ソートされたデータを取得できます。
List<String> sorted = names.stream()
.sorted()
.collect(Collectors.toList());
このようにStream.sorted()は非破壊的であり、イミュータブルな処理を意識したコードになります。最近の開発では、再利用性や安全性の面でこちらを好む開発者も多いです。
4. stream().sorted()にラムダ式を使う方法
stream().sorted()も、ラムダ式を使って柔軟にソート条件を指定できます。たとえば、長さでソートする場合は以下のように書きます。
List<String> sorted = names.stream()
.sorted((a, b) -> Integer.compare(a.length(), b.length()))
.collect(Collectors.toList());
複数条件のソートや、nullチェックなどもラムダ式と組み合わせて使うと、非常に強力です。
5. Collections.sortとStream.sortedの違いを表で比較
以下は、それぞれの使い方の違いをわかりやすくまとめた比較表です。
| 項目 | Collections.sort | Stream.sorted |
|---|---|---|
| Javaバージョン | Java5〜 | Java8〜 |
| データの扱い | 破壊的(元を変更) | 非破壊的(元はそのまま) |
| 戻り値 | void | 新しいリスト |
| パラダイム | 命令型 | 関数型 |
| 可読性 | 慣れているなら◎ | モダンで読みやすい |
6. 性能の違いと選び方のポイント
基本的にソートのアルゴリズムはTimSortで共通しており、性能差は大きくありません。ただし、大量データを扱う場合は、Stream APIによる中間操作の最適化や、並列化(parallelStream)の活用で差が出ることがあります。
並列ソートの例:
List<String> sorted = names.parallelStream()
.sorted()
.collect(Collectors.toList());
CPUコアが多い環境では、parallelStreamを使うことで高速化が見込めます。ただし、順序が保証されないことがあるため、並び順が重要な処理には注意が必要です。
7. ラムダ式+ソートの実践例(オブジェクト編)
ユーザー情報などのカスタムオブジェクトを年齢順や名前順で並べる場合も、ラムダ式とComparatorを組み合わせることで柔軟なソートが可能です。
class User {
String name;
int age;
User(String name, int age) {
this.name = name;
this.age = age;
}
}
List<User> users = Arrays.asList(
new User("Tanaka", 30),
new User("Yamada", 25),
new User("Sato", 35)
);
List<User> sorted = users.stream()
.sorted((a, b) -> Integer.compare(a.age, b.age))
.collect(Collectors.toList());
このように、ラムダ式を使ったソート処理は、ビジネスロジックでも多用されており、Javaのラムダ式とStream APIのソートを組み合わせることで、より効率的なコーディングが可能になります。
8. 結局どっちを使えばいいの?初心者向けの選び方
もしあなたがJavaに慣れておらず、既存コードがCollections.sortを使っているなら、最初はそのまま使っても問題ありません。しかし、今から新しくコードを書くなら、以下のポイントで判断しましょう。
- 元のリストを書き換えたい →
Collections.sort - 元データを変更したくない →
stream().sorted() - 並列処理したい →
parallelStream().sorted() - 読みやすく書きたい →
ラムダ式+Stream
Javaのラムダ式とStream APIをマスターすると、可読性が高く、安全性の高いコードを書くことができます。Javaのソート処理をもっとスッキリ書きたい方は、ぜひstream().sorted()を使ってみてください。
まとめ
Javaのラムダ式を使ったソート処理の全体像を振り返る
この記事では、Javaにおけるソート処理をテーマに、Collections.sortとStream.sortedの違い、そしてラムダ式やComparatorを使った柔軟な並び替え方法について詳しく学びました。Javaでリストやオブジェクトをソートする処理は、業務システムや学習用プログラムのどちらでも頻繁に登場する重要な知識です。
従来から使われてきたCollections.sortは、リストそのものを並び替える破壊的なソートであり、シンプルで分かりやすい反面、元のデータを保持したい場合には注意が必要でした。一方で、Java8以降に登場したStream APIのsortedメソッドは、元のリストを変更せずに新しいソート結果を生成するため、安全性や再利用性の面で優れています。
ラムダ式とComparatorによる柔軟な条件指定
ラムダ式を使うことで、ソート条件を非常に簡潔に記述できる点も大きなポイントです。文字列の長さで並び替えたり、数値の大小で比較したりといった処理を、匿名クラスを使わずに書けるため、コード全体の可読性が向上します。Javaのラムダ式は、ソート処理との相性が非常によく、現代的なJavaプログラミングには欠かせない要素と言えるでしょう。
また、オブジェクトのソートにおいても、ラムダ式とComparatorを組み合わせることで、年齢順、名前順、複数条件による並び替えなど、実務で必要とされるさまざまな要件に対応できます。これは、Javaのコレクション操作を理解する上で重要なステップです。
破壊的ソートと非破壊的ソートの使い分け
今回の内容で特に重要なのは、「どちらのソートを使うかは状況次第」という考え方です。元のリストを変更しても問題ない場面ではCollections.sortが有効ですし、データを安全に扱いたい場合や処理の流れを分かりやすくしたい場合にはstream().sorted()が適しています。
さらに、parallelStreamを使えば、大量データを扱う際にパフォーマンス向上が期待できるケースもあります。ただし、並列処理ならではの注意点もあるため、順序が重要な処理では慎重に選択する必要があります。このような判断力も、Javaのソート処理を理解する上で欠かせないポイントです。
まとめとしてのサンプルプログラム
ここで、今回学んだ内容を整理するために、ラムダ式とStreamを使ったソートのサンプルプログラムを改めて確認してみましょう。記事内と同じクラス構成や書き方を意識しています。
List<String> names = Arrays.asList("Tanaka", "Yamada", "Sato");
List<String> sortedNames = names.stream()
.sorted((a, b) -> a.compareTo(b))
.collect(Collectors.toList());
System.out.println(sortedNames);
このように、stream().sorted()を使うことで、元のリストを変更せずにソート結果を取得できます。ラムダ式による比較処理は直感的で、Java初心者でも理解しやすい構文です。
生徒
「Collections.sortとStream.sortedって、結果は同じでも考え方が全然違うんですね。」
先生
「そうだね。元のリストを変更するかどうかは、実務ではとても重要な判断ポイントになるよ。」
生徒
「ラムダ式を使うと、ソート条件がすごく分かりやすく書けるのも印象的でした。」
先生
「その通り。Javaのラムダ式とStream APIを組み合わせることで、読みやすくて安全なコードが書けるようになるんだ。」
生徒
「これからは、元データを残したいときはStream.sortedを選ぶように意識してみます。」
先生
「いい心がけだね。状況に応じて正しいソート方法を選べるようになると、Javaプログラミングの理解が一段深まるよ。」
今回のまとめを通して、Javaのラムダ式によるソート処理、Collections.sortとStream.sortedの違い、そして実践的な使い分けの考え方を整理できたはずです。Javaでのリスト操作やデータ処理をより深く理解するために、ぜひ何度も読み返してみてください。