Javaコレクションのソート方法まとめ|List・Set・Mapを初心者向けに徹底解説
生徒
「Javaでリストの中身を順番に並べ替えたいんですけど、どうすればいいですか?」
先生
「Javaでは、コレクションフレームワークという便利な仕組みがあって、ListやSet、Mapといったデータの集まりを簡単に並べ替える方法が用意されているんですよ。」
生徒
「アルファベット順や数値の大きさ順に並べるのは難しいんでしょうか?」
先生
「いいえ、基本を覚えればとても簡単です!それでは、具体的なソートの手順を一緒に学習していきましょう。」
1. Javaのコレクションフレームワークにおけるソートの基本
Javaでデータを扱う際に欠かせないのが、複数の要素をまとめて管理する「コレクションフレームワーク」です。データの集まりを特定のルールに基づいて並び替えることを「ソート」と呼びます。プログラミングにおいて、名前を五十音順に並べたり、商品の価格を安い順に表示したりする機能は非常に頻繁に使われます。Javaでは、主に標準ライブラリであるjava.util.Collectionsクラスや、java.util.Listインターフェースのメソッドを利用してソートを実現します。初心者の方は、まず最も基本的な「昇順(小さい順)」と「降順(大きい順)」の概念を理解することが大切です。
ソート対象となる主要なインターフェースには、List、Set、Mapがありますが、それぞれ特性が異なります。特にListは要素の順番を保持するため、最もソート処理を行いやすい構造をしています。一方で、SetやMapは本来「順序を持たない」あるいは「特定の規則で保持する」仕組みのため、ソートを行うには少し工夫が必要です。この記事では、それぞれのデータ型に応じた最適なソート方法を詳しく掘り下げていきます。
2. Listをソートする最も簡単な方法:Collections.sortの使い方
Javaで最も一般的に使われるList(ArrayListなど)をソートする場合、Collections.sort()メソッドを使用するのが一番の近道です。このメソッドを使うと、要素が持つ「自然順序付け」に従って並び替えが行われます。数値であれば昇順、文字列であれば辞書順(アルファベット順や五十音順)になります。以下のプログラムでは、整数のリストを昇順に並べ替える方法を確認してみましょう。
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(numbers);
System.out.println("ソート後: " + numbers);
}
}
ソート前: [50, 10, 30]
ソート後: [10, 30, 50]
このように、一行のコードで簡単に並び替えが完了します。内部的には、要素同士を比較して順番を入れ替える処理が自動的に行われています。ただし、この方法はリストそのものを直接書き換える(破壊的な変更)ため、元の順番を保持したい場合は、元のリストをコピーしてからソートを行うように注意しましょう。
3. 逆順(降順)に並べ替える方法
「大きい順に並べたい」という場合には、降順ソートを利用します。Javaでは、Collections.reverseOrder()というメソッドを使って、ソート順を逆に指定することができます。これは「Comparator」と呼ばれる比較規則を定義するオブジェクトを利用した手法です。自分自身で複雑な並び替えのルールを作りたいときにもこの考え方が応用されます。まずは、基本となる文字列の降順ソートを見ていきましょう。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ReverseSortExample {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Orange");
fruits.add("Banana");
// 降順にソート
Collections.sort(fruits, Collections.reverseOrder());
System.out.println("降順ソートの結果: " + fruits);
}
}
降順ソートの結果: [Orange, Banana, Apple]
実行結果を見ると、アルファベットの逆順になっていることがわかります。このように、sortメソッドの第二引数に並び替えのルールを渡すことで、自由自在に順番をコントロールできるようになります。Java 8以降では、Listインターフェース自体にsortメソッドが追加されたため、fruits.sort(Collections.reverseOrder())と記述することも一般的になっています。新しい書き方にも慣れておくと、よりモダンなJavaプログラムが書けるようになります。
4. Set(集合)をソートするための手順とTreeSetの活用
次にSet(セット)について解説します。HashSetなどの通常のSetは、要素の重複を許さないという特徴がありますが、要素の並び順は保証されていません。そのため、Setに入っているデータをソートしたい場合は、主に二つのアプローチがあります。一つは「一度Listに変換してからソートする」方法、もう一つは「最初からソート機能を持つTreeSetを使用する」方法です。TreeSetは、要素を追加した瞬間に自動的に適切な順序で並べてくれる非常に便利なクラスです。
import java.util.Set;
import java.util.TreeSet;
public class SetSortExample {
public static void main(String[] args) {
// TreeSetを使うと自動的に昇順になる
Set<String> set = new TreeSet<>();
set.add("Java");
set.add("Python");
set.add("Ruby");
set.add("PHP");
System.out.println("自動ソートされたSet: " + set);
}
}
自動ソートされたSet: [Java, PHP, Python, Ruby]
このように、TreeSetを使うと明示的にソート処理を呼び出す必要がありません。ただし、TreeSetは要素を追加するたびに並び替え計算を行うため、大量のデータを追加する場合は処理速度に影響が出ることがあります。データの量や用途に合わせて、HashSetからListへ移してソートするのか、最初からTreeSetを使うのかを選択できるようになりましょう。プログラミング初学者のうちは、まずは「Setには順序がないが、TreeSetなら順番がつく」と覚えておけば十分です。
5. Mapをキーや値でソートする方法:TreeMapとStream API
Map(マップ)は、キー(Key)と値(Value)のペアでデータを保持する構造です。HashMapは順序を保持しませんが、Mapをソートしたい場面も多くあります。キーによってソートしたい場合は、Setの時と同様にTreeMapを利用するのが最も簡単です。TreeMapはキーの自然順序付けに従って自動的にペアを並べてくれます。一方で、値(Value)に基づいてソートしたい場合は、少し高度なテクニックが必要になります。Java 8から導入された「Stream API」を使うのが現代的な手法です。
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class MapSortExample {
public static void main(String[] args) {
Map<String, Integer> scores = new HashMap<>();
scores.put("田中", 80);
scores.put("佐藤", 95);
scores.put("鈴木", 70);
// 値(スコア)で昇順にソートする例
Map<String, Integer> sortedMap = scores.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));
System.out.println("スコア順にソート: " + sortedMap);
}
}
スコア順にソート: {鈴木=70, 田中=80, 佐藤=95}
上記のプログラムでは、一度Mapの中身を「ストリーム」というデータの流れに変換し、そこで並び替えを行った後に、再びMapに戻しています。その際、LinkedHashMapを使用しているのがポイントです。通常のHashMapに戻してしまうと、せっかくソートした順番が再びバラバラになってしまうからです。LinkedHashMapは追加した順番を保持する性質を持っているため、ソート結果を維持するのに適しています。初心者には少し難しいかもしれませんが、「MapのソートはStreamを使うと柔軟にできる」というイメージを持っておきましょう。
6. 自作クラスをソートするためのComparableとComparatorの違い
これまではIntegerやStringといったJavaの標準クラスを扱ってきましたが、実際の開発では自分で作成した「社員クラス」や「商品クラス」のリストをソートすることが多いです。自作クラスにはデフォルトの並び順が存在しないため、Javaに「何をもって大小を比較するか」を教えてあげる必要があります。そのための方法が二つあります。一つ目が、クラス自体に比較ルールを組み込むComparableインターフェースの実装。二つ目が、外側から比較ルールを渡すComparatorインターフェースの利用です。
一般的に、そのクラスの「標準的な並び順(ID順など)」が決まっている場合はComparableを使い、状況に応じて「価格順」や「発売日順」など複数のルールを使い分けたい場合はComparatorを使います。特に近年はラムダ式を使って、その場で簡潔にComparatorを記述する方法が主流です。例えば、list.sort((a, b) -> a.getPrice() - b.getPrice())のように書くことで、非常にスッキリとしたコードで自作オブジェクトの並び替えが可能になります。このようにソートの仕組みを理解することで、より実戦的なプログラムが組めるようになります。
7. ラムダ式を使ったモダンなソートの書き方
Java 8以降、ラムダ式の登場によってコレクションの操作は劇的に進化しました。以前は匿名クラスという複雑な記述が必要だったソート処理も、今では数文字で記述できます。ラムダ式を使う最大のメリットは、コードの可読性が向上し、意図が明確になることです。特にComparator.comparing()といったメソッドを組み合わせることで、「どのフィールドを見てソートするか」を直感的に指定できるようになります。
例えば、文字列の長さに注目してソートしたい場合、以前なら専用のクラスを作る必要がありましたが、現在はComparator.comparingInt(String::length)と書くだけで済みます。このように、Javaのバージョンアップに伴って追加された便利な機能を積極的に取り入れることで、バグが少なくメンテナンスしやすいコードを書くことができます。初心者の方はまずCollections.sort()から入り、徐々にラムダ式やStream APIを使った高度なソートへとステップアップしていくのが理想的です。
8. コレクション操作で知っておきたいパフォーマンスと注意点
ソート処理は非常に便利ですが、実行時のパフォーマンスについても少し意識しておく必要があります。データの件数が数件から数百件程度であれば気にする必要はありませんが、数万件、数百万件という膨大なデータをソートする場合、並び替えのアルゴリズムがコンピュータの計算リソースを大量に消費します。Javaの標準ソートアルゴリズムは非常に効率的に設計されていますが、それでも不必要なタイミングで何度もソートを繰り返すような実装は避けるべきです。
また、注意点として「null」の扱いがあります。リストの中にnullが含まれている状態でソートを実行すると、NullPointerExceptionというエラーが発生してプログラムが停止してしまいます。実務では、データの中にnullが混ざる可能性があるため、Comparator.nullsFirst()やnullsLast()といったメソッドを使って、nullを最初にするか最後にするかをあらかじめ定義しておくのが安全なプログラミングのコツです。こうした細かい配慮ができるようになると、初心者から一歩抜け出したエンジニアへと成長できるでしょう。