Java Listをソートする方法まとめ|Comparator・ラムダ式・Stream活用
生徒
「JavaのListに入っているデータを、数字の小さい順や名前の順に並べ替えたいのですが、どうすればいいですか?」
先生
「Javaでは、Collections.sortメソッドや、Listインターフェースのsortメソッドを使って簡単に並べ替えることができますよ。」
生徒
「初心者でも簡単にできる方法はありますか?最近よく聞くラムダ式やStreamというのも気になります!」
先生
「もちろんです。基本から最新の書き方まで、順番に学んでいきましょう。実はとてもシンプルに書けるんですよ!」
1. JavaのListソートの基本概念を知ろう
Javaのプログラミングでは、複数のデータをまとめて扱うために「List(リスト)」という仕組みを頻繁に使います。Listは、データを順番に並べて保持できるのが特徴です。この「順番」を目的に応じて入れ替える処理を「ソート(並べ替え)」と呼びます。
例えば、買い物アプリで商品を「価格が安い順」に表示したり、名簿を「あいうえお順」で並べたりする場面を想像してみてください。人間にとって自然な並びでも、コンピュータには「どういうルールで並べるのか」を明確に伝える必要があります。
JavaでListをソートする考え方は、大きく分けて2つあります。1つ目は「自然順序付け」です。これは、数値であれば小さい順、文字列であればアルファベット順や五十音順といった、データ型ごとにあらかじめ決められている標準的な並び方です。特に指定をしなければ、多くの場合この自然順序が使われます。
2つ目は「Comparator(比較器)」を使ったソートです。Comparatorを使うと、「文字数が短い順」「価格が高い順」「日付が新しい順」など、自分で自由にルールを決めて並べ替えることができます。実務ではこちらを使うケースが非常に多く、Javaのソートを理解するうえで欠かせない考え方です。
プログラミング未経験の方は、まず「Listには順番があり、その順番をルールに従って入れ替えられる」という点を押さえれば十分です。Javaでは、このソート処理を簡単に書ける便利な仕組みが用意されており、難しいアルゴリズムを自分で考える必要はありません。
次の簡単なサンプルは、数値が入ったListを小さい順に並べ替えるイメージをつかむためのものです。細かい文法が分からなくても、「sortを呼ぶと並びが変わる」という感覚で読み進めてみてください。
import java.util.ArrayList;
import java.util.List;
public class SimpleSortImage {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(3);
numbers.add(1);
numbers.add(2);
// 並べ替え前
System.out.println(numbers);
// Listを小さい順にソート
numbers.sort(null);
// 並べ替え後
System.out.println(numbers);
}
}
この例では、sortを呼び出すだけで、Listの中身が「1, 2, 3」という順番に並び替えられます。まずはこの基本的な動きを理解することが、JavaのListソートを学ぶ第一歩になります。
2. Collections.sortを使った最もスタンダードな方法
Javaの初期から使われている最も一般的な方法が、java.util.Collectionsクラスのsortメソッドを使用する方法です。このメソッドは、引数に渡したListを破壊的に(元のリストの中身を直接)書き換えてソートします。
以下のコード例では、整数のリストを昇順(小さい順)に並べ替える基本的な書き方を示します。インポート文が必要になる点に注意しましょう。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class BasicSortExample {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(50);
numbers.add(10);
numbers.add(30);
System.out.println("ソート前: " + numbers);
// Collections.sortを使って昇順にソート
Collections.sort(numbers);
System.out.println("ソート後: " + numbers);
}
}
ソート前: [50, 10, 30]
ソート後: [10, 30, 50]
この方法は非常にシンプルで分かりやすいのが特徴です。文字列のリスト(StringのList)に対しても同様に使うことができ、その場合は辞書順(あいうえお順やABC順)に並びます。もし降順(大きい順)にしたい場合は、Collections.reverseOrder()を第二引数に渡すことで実現できます。
3. List.sortメソッドとComparatorの活用
Java 8以降では、Listインターフェース自体にsortメソッドが追加されました。これにより、Collections.sort(list)と書く代わりに、list.sort(...)と直感的に記述できるようになりました。このメソッドを使う際は、どのように並べ替えるかを指定する「Comparator」を渡すのが一般的です。
Comparatorは「比較器」という意味で、2つの要素を比べてどちらを前にするかを決定する役割を持ちます。単純な昇順であればComparator.naturalOrder()、降順であればComparator.reverseOrder()を指定します。nullが含まれる可能性があるリストをソートする場合には、Comparator.nullsFirstといった便利なメソッドも用意されています。これにより、実行時にエラー(NullPointerException)が発生するのを防ぎつつ、安全に並べ替えを行うことができます。
4. ラムダ式を使ったスマートなソート記述
Java 8の目玉機能である「ラムダ式」を使うと、独自のソート条件を非常に簡潔に書くことができます。例えば、文字列のリストを「文字の長さが短い順」に並べ替えたい場合、以前は匿名クラスという複雑な書き方が必要でしたが、ラムダ式なら直感的に記述可能です。
ラムダ式は(引数) -> { 処理 }という形式で書きます。ソートにおいては、2つの要素(aとb)を比較するロジックをこの形式で流し込みます。では、実際に文字列の長さを基準にしたソートのコードを見てみましょう。
import java.util.ArrayList;
import java.util.List;
public class LambdaSortExample {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Kiwi");
fruits.add("Orange");
// ラムダ式を使って文字列の長さ順にソート
fruits.sort((a, b) -> a.length() - b.length());
System.out.println("長さ順にソート: " + fruits);
}
}
長さ順にソート: [Kiwi, Apple, Banana, Orange]
このコードでは、a.length() - b.length()の結果が負であればaが前、正であればbが前というルールに基づいて動いています。このように、特定のルールに基づいた並べ替えをたった一行で表現できるのがラムダ式の強みです。初心者の方も、この書き方に慣れるとJavaプログラミングがぐっと楽しくなるはずです。
5. Stream APIを使った非破壊的なソート
これまでに紹介した方法は、元のリストそのものを並べ替えてしまう「破壊的」な手法でした。しかし、実務では「元のリストはそのまま残しておき、新しく並べ替えたリストを作成したい」という場面が多くあります。そこで役立つのが「Stream API」です。
Stream APIのsorted()メソッドを使用すると、元のデータを変更せずに、ソート済みの新しいストリームを生成できます。最後にcollect(Collectors.toList())を使ってリスト形式に戻すのが一般的な流れです。この手法は、元のデータを壊したくない関数型プログラミング的なアプローチにおいて非常に重要です。また、フィルター(抽出)やマップ(変換)といった他の処理と組み合わせて使うことができるため、複雑なデータ加工も一気に行うことができます。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamSortExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("田中", "佐藤", "鈴木", "伊藤");
// Stream APIを使ってソートし、新しいリストを作成
List<String> sortedNames = names.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("元のリスト: " + names);
System.out.println("ソート後リスト: " + sortedNames);
}
}
元のリスト: [田中, 佐藤, 鈴木, 伊藤]
ソート後リスト: [伊藤, 佐藤, 鈴木, 田中]
6. 自作クラスのリストをソートする方法
実際の開発では、StringやIntegerだけでなく、自分で作成したクラス(例えば「Employee」や「Product」クラス)のリストをソートすることがほとんどです。このような場合、どのフィールドを基準にソートするかをコンピュータに教えてあげる必要があります。
Comparator.comparingメソッドを使うと、自作クラスの特定のフィールドをキーにしたソートが驚くほど簡単に実装できます。例えば、社員クラスに「ID」と「名前」がある場合、IDでソートしたり名前でソートしたりといった切り替えがスムーズに行えます。以下の例では、商品の価格をもとにソートする処理を記述しています。
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
class Product {
String name;
int price;
Product(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return name + ":" + price + "円";
}
}
public class ObjectSortExample {
public static void main(String[] args) {
List<Product> products = new ArrayList<>();
products.add(new Product("ノートPC", 120000));
products.add(new Product("マウス", 2500));
products.add(new Product("キーボード", 8000));
// 価格の安い順にソート
products.sort(Comparator.comparing(p -> p.price));
for (Product p : products) {
System.out.println(p);
}
}
}
マウス:2500円
キーボード:8000円
ノートPC:120000円
7. 複数の条件を組み合わせた高度なソート
「まずは点数が高い順に並べ、同じ点数の人がいたら名前順に並べたい」といった複数の条件を組み合わせたいこともあります。JavaのComparatorには、こうした複雑な要望に応えるためのthenComparingという便利なメソッドが用意されています。
これを使えば、第一条件を指定した後に、ドットで繋いで第二条件、第三条件と追加していくことができます。コードの可読性も高く、後から条件を追加したり変更したりするのも容易です。実務のアプリケーション開発では、ユーザーの使い勝手を向上させるために、こうした細かいソート順の制御が求められることが多いため、ぜひ覚えておきたいテクニックです。また、逆順にしたい場合はreversed()を組み合わせることで、柔軟な並び替えルールを構築できます。
8. ソート時の注意点とパフォーマンス
Listのソートを行う際に気をつけなければならないのが、変更不可能なリスト(Unmodifiable List)に対する操作です。List.of()やCollections.unmodifiableList()で作成されたリストに対してsort()メソッドを呼び出すと、実行時にエラー(UnsupportedOperationException)が発生します。ソートを行う必要がある場合は、必ずnew ArrayList<>(originalList)のようにして、中身を変更可能な新しいリストにコピーしてから行うようにしましょう。
また、非常に大量のデータをソートする場合のパフォーマンスについても少し触れておきます。Javaの標準ソートアルゴリズムは非常に効率的(TimSortなどが採用されています)ですが、それでも要素数が数百万件を超えるような場合は、処理時間が無視できなくなります。そのような場合は、Stream APIのparallelStream()を利用した並列ソートを検討することもあります。しかし、並列処理はオーバーヘッドもあるため、基本的には通常のソートで十分なケースがほとんどです。まずは正しい書き方をマスターし、必要に応じて最適化を考えていくのが良いでしょう。
9. 実践的なソートの使い分けガイド
最後に、どの方法をいつ使うべきかの指針を整理しましょう。最もシンプルな数値や文字列の破壊的ソートであればCollections.sort()やList.sort(null)で十分です。独自のルールを適用したい場合は、ラムダ式を用いたList.sort((a, b) -> ...)が適しています。一方、元のデータを保持したまま加工後のリストを得たい場合や、他のデータ処理とチェーンさせたい場合はStream APIのsorted()一択となるでしょう。
初心者の方は、まず自分が今扱っているリストを「変えてもいいのか(破壊的)」「変えてはいけないのか(非破壊的)」を意識することから始めてみてください。それが決まれば、自ずと使うべきメソッドが見えてくるはずです。Javaは非常に型に厳格で安全な言語ですが、ソート周りのライブラリは驚くほど柔軟に作られています。色々なパターンを試して、自在にデータを操れるようになりましょう。ここまで学んだ内容を実際のコードに取り入れることで、あなたのプログラムの品質は一段と向上するはずです。
まとめ
Java Listソートの総復習と実務での活用ポイント
本記事では、JavaにおけるListのソート方法について、基礎から応用まで段階的に整理してきました。Listの並べ替えは、単なるテクニックではなく、データ処理やユーザー体験を大きく左右する重要な処理です。特に業務システムやWebアプリケーション開発では、検索結果や一覧表示の順序が使いやすさに直結するため、正確かつ柔軟なソート処理の理解が欠かせません。
まず基本となるのが、Collections.sortを用いたシンプルなソートです。この方法は直感的で分かりやすく、整数や文字列の並べ替えには非常に適しています。Java初心者にとっては、最初に身につけるべき基本操作と言えるでしょう。一方で、Java八以降ではList.sortメソッドが追加され、より自然な書き方が可能になりました。これにより、コードの可読性が向上し、メンテナンス性も高まっています。
さらに、Comparatorを活用することで、単純な昇順や降順だけでなく、複雑な条件による並べ替えが実現できます。例えば、名前順や価格順、日付順など、業務要件に応じた柔軟なソートが可能になります。Comparator.comparingやthenComparingを使うことで、複数条件の組み合わせも簡単に実装できる点は、実務において非常に重要です。
また、ラムダ式の導入によって、ソート処理はさらに簡潔に記述できるようになりました。従来の匿名クラスに比べて圧倒的に短く書けるため、コードの見通しが良くなり、バグの発生も抑えられます。特に、初心者から中級者へステップアップする際には、このラムダ式の理解が大きな分岐点になります。
そして、Stream APIを使った非破壊的ソートは、現代的なJava開発において欠かせない考え方です。元のデータを変更せずに新しいリストを生成することで、安全で予測可能な処理が実現できます。関数型プログラミングの考え方に基づいたこの手法は、大規模開発やチーム開発でも特に重宝されます。
実務では、自作クラスのリストを扱う場面がほとんどです。そのため、フィールドを基準としたソートや、複数条件による並べ替えを自在に扱えるようになることが重要です。例えば、社員一覧を年齢順に並べ替えたり、商品一覧を価格と名前で整理したりといった処理は、日常的に発生します。
最後に注意点として、変更不可能なリストに対してソートを行うとエラーが発生する点や、大量データの処理におけるパフォーマンスも理解しておく必要があります。特に、parallelStreamを使った並列処理は強力ですが、適切な場面で使うことが求められます。
サンプルコードで復習する
import java.util.*;
import java.util.stream.Collectors;
public class SummarySortExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("山田", "佐藤", "鈴木", "田中");
// 昇順ソート
List<String> sorted = new ArrayList<>(names);
Collections.sort(sorted);
// 降順ソート
sorted.sort(Comparator.reverseOrder());
// ラムダ式で文字数順
sorted.sort((a, b) -> a.length() - b.length());
// Streamで非破壊的ソート
List<String> streamSorted = names.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sorted);
System.out.println(streamSorted);
}
}
[山田, 佐藤, 鈴木, 田中]
[佐藤, 鈴木, 田中, 山田]
生徒
今回の内容で、JavaのListソートにはいろいろな方法があることが分かりました。特にラムダ式を使うとコードがかなり短くなるのが印象的でした。
先生
その通りですね。基本のCollections.sortから始まり、List.sort、Comparator、そしてStream APIまで理解できれば、ほとんどの並べ替え処理に対応できます。
生徒
どの方法を使えばいいのか迷いそうですが、判断基準はありますか。
先生
大切なのは、元のリストを変更してよいかどうかです。変更してよいならCollectionsやList.sort、変更したくないならStreamを使うと覚えておくと良いでしょう。
生徒
なるほど、処理の目的によって使い分けるのですね。自作クラスのソートも理解できたので、実務でも使えそうです。
先生
いいですね。実際の開発では複数条件のソートも多いので、ComparatorのthenComparingもぜひ活用してください。今回の内容を繰り返し書いて慣れていくことが上達への近道です。