Javaの配列をソートする方法を徹底解説!Arrays.sortとComparatorの使い方
生徒
「Javaで配列の要素を並び替えたいんですけど、どうすればいいですか?」
先生
「Javaでは、Arrays.sortメソッドを使うことで、配列を簡単にソートすることができますよ。数値や文字列など、さまざまなデータ型に対応しています。」
生徒
「昇順だけじゃなくて、降順にもできるんですか?」
先生
「もちろんです!Comparatorを使えば、降順や独自の並び替えルールも自由に設定できます。それでは、基本から順番に見ていきましょう!」
1. Arrays.sortメソッドとは?
JavaのArrays.sortメソッドは、配列の中身を自動で並び替えてくれる、とても基本的で重要な機能です。たとえば「数字を小さい順にしたい」「文字を名前順に並べたい」といった場面で使われます。プログラミングが初めての方でも、難しい処理を書かなくても使えるのが大きな特徴です。
Arrays.sortはjava.util.Arraysクラスに用意されており、数値(intやdoubleなど)や文字列(String)、オブジェクト配列にも対応しています。内部ではJavaが最適な高速アルゴリズムを自動で選択してくれるため、配列の要素が多くなっても安心して利用できます。
使い方はとてもシンプルで、ソートしたい配列をそのまま引数に渡すだけです。ただし注意点として、このメソッドは元の配列の順番を直接書き換えます。つまり、並び替え前の状態は残りません。最初のうちは「配列を渡すだけで順番が変わる」と覚えておけば十分です。
import java.util.Arrays;
public class SimpleSortExample {
public static void main(String[] args) {
int[] numbers = {3, 1, 4, 2};
// 配列を昇順に並び替える
Arrays.sort(numbers);
// 結果を表示
System.out.println(Arrays.toString(numbers));
}
}
このサンプルでは、バラバラな数字が自動的に「小さい順」に並び替えられます。Arrays.sortを使うことで、複雑な処理を考えなくても、配列のソートを簡単に実現できる点が初心者にとって大きなメリットです。
2. 基本的な使い方:int型配列のソート
まずは最も基本的な、int型の配列を昇順にソートする方法を見ていきましょう。Arrays.sortメソッドに配列を渡すだけで、元の配列が直接書き換えられます。この動作は「破壊的メソッド」と呼ばれ、元の配列の順序が変更されることに注意してください。
import java.util.Arrays;
public class ArraySortBasic {
public static void main(String[] args) {
int[] numbers = {45, 12, 78, 23, 56, 9, 34};
System.out.println("ソート前: " + Arrays.toString(numbers));
Arrays.sort(numbers);
System.out.println("ソート後: " + Arrays.toString(numbers));
}
}
実行結果は以下のようになります。
ソート前: [45, 12, 78, 23, 56, 9, 34]
ソート後: [9, 12, 23, 34, 45, 56, 78]
このように、配列の要素が小さい順に並び替えられました。Arrays.toStringメソッドを使うと、配列の内容を見やすく表示できます。プリミティブ型の配列では、デフォルトで昇順にソートされます。
3. 文字列配列のソート
String型の配列も、Arrays.sortメソッドで簡単にソートできます。文字列の場合は、辞書順(アルファベット順)で並び替えられます。日本語の文字列も、文字コードの順序に従ってソートされます。大文字と小文字は区別され、大文字が小文字よりも先に並びます。
import java.util.Arrays;
public class StringArraySort {
public static void main(String[] args) {
String[] fruits = {"バナナ", "りんご", "みかん", "いちご", "ぶどう"};
System.out.println("ソート前: " + Arrays.toString(fruits));
Arrays.sort(fruits);
System.out.println("ソート後: " + Arrays.toString(fruits));
}
}
実行結果は以下のようになります。
ソート前: [バナナ, りんご, みかん, いちご, ぶどう]
ソート後: [いちご, バナナ, みかん, りんご, ぶどう]
日本語の場合は、五十音順で並び替えられます。英語の文字列の場合は、アルファベット順になります。文字列の長さに関係なく、最初の文字から順番に比較されていきます。
4. 配列の一部分だけをソートする方法
Arrays.sortメソッドには、配列の特定の範囲だけをソートするオーバーロードされたバージョンもあります。開始インデックスと終了インデックスを指定することで、配列の一部分のみを並び替えることができます。終了インデックスは含まれない点に注意してください。
この機能は、配列の一部分だけを処理したい場合や、既にソート済みの部分を保持したい場合に便利です。大きな配列の中で、特定の範囲だけを並び替えたいときに活用できます。
import java.util.Arrays;
public class PartialArraySort {
public static void main(String[] args) {
int[] numbers = {100, 50, 80, 30, 60, 20, 90, 40};
System.out.println("ソート前: " + Arrays.toString(numbers));
Arrays.sort(numbers, 2, 6);
System.out.println("ソート後: " + Arrays.toString(numbers));
}
}
実行結果は以下のようになります。
ソート前: [100, 50, 80, 30, 60, 20, 90, 40]
ソート後: [100, 50, 20, 30, 60, 80, 90, 40]
この例では、インデックス2から6の手前まで(つまり要素80、30、60、20の部分)だけがソートされています。最初の2つの要素(100、50)と最後の2つの要素(90、40)は元の順序のまま保持されています。
5. Comparatorを使った降順ソート
配列を降順(大きい順)にソートしたい場合は、Comparatorを使用します。しかし、プリミティブ型の配列ではComparatorを直接使用できないため、ラッパークラス(Integer、Doubleなど)の配列を使う必要があります。Collections.reverseOrder()を使うことで、簡単に降順ソートを実現できます。
Comparatorは、要素同士を比較するためのルールを定義するインターフェースです。これを活用することで、標準的な昇順以外の並び替えが可能になります。独自の比較ロジックを実装することもできます。
import java.util.Arrays;
import java.util.Collections;
public class DescendingSort {
public static void main(String[] args) {
Integer[] numbers = {45, 12, 78, 23, 56, 9, 34};
System.out.println("ソート前: " + Arrays.toString(numbers));
Arrays.sort(numbers, Collections.reverseOrder());
System.out.println("降順ソート後: " + Arrays.toString(numbers));
}
}
実行結果は以下のようになります。
ソート前: [45, 12, 78, 23, 56, 9, 34]
降順ソート後: [78, 56, 45, 34, 23, 12, 9]
この方法では、配列の型をint[]ではなくInteger[]にする必要があります。Collections.reverseOrder()は、自然順序とは逆の順序で要素を比較するComparatorを返します。
6. カスタムComparatorで独自のソート順を実現
Comparatorを自分で実装することで、独自のソート順を定義できます。たとえば、文字列の長さでソートしたり、特定の条件に基づいて並び替えたりすることが可能です。ラムダ式を使うと、コードを簡潔に書くことができます。
Comparatorのcompareメソッドは、2つの要素を比較して、負の数、ゼロ、正の数のいずれかを返します。負の数を返すと第一引数が前に、正の数を返すと第二引数が前に配置されます。
import java.util.Arrays;
import java.util.Comparator;
public class CustomComparatorSort {
public static void main(String[] args) {
String[] words = {"Java", "プログラミング", "配列", "ソート", "アルゴリズム"};
System.out.println("ソート前: " + Arrays.toString(words));
Arrays.sort(words, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});
System.out.println("文字列の長さでソート: " + Arrays.toString(words));
Arrays.sort(words, (s1, s2) -> Integer.compare(s2.length(), s1.length()));
System.out.println("文字列の長さで降順ソート: " + Arrays.toString(words));
}
}
実行結果は以下のようになります。
ソート前: [Java, プログラミング, 配列, ソート, アルゴリズム]
文字列の長さでソート: [Java, 配列, ソート, アルゴリズム, プログラミング]
文字列の長さで降順ソート: [プログラミング, アルゴリズム, Java, ソート, 配列]
最初は匿名クラスを使ってComparatorを実装し、その後ラムダ式を使ってより簡潔に記述しています。ラムダ式を使うと、コードの可読性が向上し、記述量も削減できます。
7. 多次元配列のソート
二次元配列をソートする場合も、Comparatorを使用します。配列の特定の要素を基準にソートすることができます。たとえば、成績表のデータで点数順に並び替えたり、座標データをx座標やy座標で並び替えたりする場合に使用します。
多次元配列の場合、各行が一つの要素として扱われます。そのため、どの列を基準にソートするかをComparatorで明示的に指定する必要があります。これにより、複雑なデータ構造でも柔軟にソートできます。
import java.util.Arrays;
import java.util.Comparator;
public class TwoDimensionalArraySort {
public static void main(String[] args) {
int[][] students = {
{1, 85},
{2, 92},
{3, 78},
{4, 95},
{5, 88}
};
System.out.println("ソート前:");
for (int[] student : students) {
System.out.println("ID: " + student[0] + ", 点数: " + student[1]);
}
Arrays.sort(students, Comparator.comparingInt(a -> a[1]));
System.out.println("\n点数の昇順でソート後:");
for (int[] student : students) {
System.out.println("ID: " + student[0] + ", 点数: " + student[1]);
}
Arrays.sort(students, (a, b) -> Integer.compare(b[1], a[1]));
System.out.println("\n点数の降順でソート後:");
for (int[] student : students) {
System.out.println("ID: " + student[0] + ", 点数: " + student[1]);
}
}
}
実行結果は以下のようになります。
ソート前:
ID: 1, 点数: 85
ID: 2, 点数: 92
ID: 3, 点数: 78
ID: 4, 点数: 95
ID: 5, 点数: 88
点数の昇順でソート後:
ID: 3, 点数: 78
ID: 1, 点数: 85
ID: 5, 点数: 88
ID: 2, 点数: 92
ID: 4, 点数: 95
点数の降順でソート後:
ID: 4, 点数: 95
ID: 2, 点数: 92
ID: 5, 点数: 88
ID: 1, 点数: 85
ID: 3, 点数: 78
この例では、生徒のIDと点数を持つ二次元配列を、点数を基準にソートしています。Comparator.comparingIntを使うと、整数の値で比較するComparatorを簡単に作成できます。
8. ソートのパフォーマンスと注意点
Arrays.sortメソッドは、内部的にデュアルピボットクイックソートという高速なアルゴリズムを使用しています。平均的な計算量はO(n log n)で、大量のデータでも効率的に処理できます。ただし、最悪の場合でもO(n log n)の性能が保証されるように、イントロソートという改良版が実装されています。
配列のサイズが小さい場合は、挿入ソートなどのシンプルなアルゴリズムが使用されることもあります。これは、小さなデータセットではシンプルなアルゴリズムの方が高速に動作するためです。Javaの実装は、データサイズに応じて最適なアルゴリズムを自動的に選択します。
注意点として、Arrays.sortは元の配列を直接変更します。元の順序を保持したい場合は、事前に配列をコピーしておく必要があります。Arrays.copyOfメソッドやcloneメソッドを使って配列のコピーを作成できます。また、並列処理が必要な場合は、Arrays.parallelSortメソッドを使用することで、マルチコアプロセッサの性能を活用できます。
9. プリミティブ型とラッパークラスの使い分け
配列のソートでは、プリミティブ型とラッパークラスの違いを理解することが重要です。プリミティブ型(int、double、charなど)の配列は、Comparatorを使用できません。降順ソートや独自の比較ロジックを実装したい場合は、ラッパークラス(Integer、Double、Characterなど)を使用する必要があります。
プリミティブ型の配列をラッパークラスの配列に変換するには、ループを使って各要素を変換するか、Java 8以降であればストリームAPIを活用できます。逆に、ラッパークラスの配列をプリミティブ型に戻すことも可能です。ただし、ラッパークラスはメモリ消費量が大きくなるため、大量のデータを扱う場合はパフォーマンスに注意が必要です。
パフォーマンスが重要な場合は、プリミティブ型を使用して昇順ソートを行い、必要に応じて配列を逆順にする方法もあります。配列を逆順にするには、ループを使って先頭と末尾の要素を交換していく処理を実装します。この方法は、メモリ効率が良く、高速に動作します。
10. 実践的な応用例とテクニック
配列のソート機能は、実際のプログラミングでさまざまな場面で活用されます。データ分析では、値の分布を確認するために配列をソートしてから中央値や四分位数を計算します。検索アルゴリズムでは、ソート済みの配列に対して二分探索を適用することで、高速な検索が可能になります。
複数の条件でソートしたい場合は、ComparatorのthenComparingメソッドを使用します。たとえば、まず点数でソートし、点数が同じ場合は名前でソートするといった処理が簡単に実現できます。このメソッドチェーンを使うと、複雑なソート条件を直感的に記述できます。
安定ソートが必要な場合は、Arrays.sortが安定であることを確認してください。オブジェクト配列の場合は安定ソートですが、プリミティブ型配列の場合は安定性が保証されていません。同じ値を持つ要素の順序を保持する必要がある場合は、オブジェクト配列を使用するか、独自のソートアルゴリズムを実装します。
また、nullを含む配列をソートする場合は、Comparator.nullsFirstやComparator.nullsLastを使用して、nullの扱いを明示的に指定できます。これにより、NullPointerExceptionを回避しながら、安全にソート処理を実行できます。エラーハンドリングを適切に行うことで、堅牢なプログラムを作成できます。
まとめ
本記事では、Javaにおける配列のソート方法について、基本から応用まで段階的に解説してきました。まずはArrays.sortメソッドの基本的な使い方を理解し、int型配列やString型配列を簡単に昇順に並び替える方法を学びました。配列をそのまま引数に渡すだけでソートが完了する手軽さは、Javaの標準ライブラリの強力さを実感できるポイントです。また、このメソッドが元の配列を直接書き換える破壊的メソッドである点も重要な知識です。
次に、配列の一部分のみをソートする方法を理解することで、より柔軟なデータ操作が可能になることを確認しました。特定の範囲だけを並び替えることで、効率的な処理や部分的なデータ整形が実現できます。大規模な配列データを扱う際には、このような細かい制御がパフォーマンス改善にもつながります。
さらに、Comparatorを利用した降順ソートやカスタムソートについても詳しく解説しました。特にラッパークラスを使用する必要がある点や、Collections.reverseOrderを活用することで簡単に降順ソートが実現できる点は、実務でも頻繁に使われる重要なテクニックです。独自の並び替えルールを定義することで、単純な数値順や辞書順にとどまらない高度なデータ処理が可能になります。
カスタムComparatorでは、匿名クラスやラムダ式を使って簡潔にコードを書く方法を学びました。特にラムダ式を使うことで、コードの可読性が大きく向上し、現代的なJavaプログラミングスタイルに近づけることができます。文字列の長さでソートする例や、降順に並び替える例は、応用の幅を広げる良い練習になります。
多次元配列のソートでは、特定の列を基準にして並び替える方法を理解しました。これは実際の業務システムやデータ分析において非常に重要なスキルです。例えば、成績データや売上データなどを特定の項目で並び替える場面は多く、Comparatorを適切に使うことで柔軟なデータ処理が可能になります。
また、ソートアルゴリズムのパフォーマンスや内部実装についても触れ、Arrays.sortが高速なアルゴリズムを採用していることを理解しました。計算量やアルゴリズムの特性を知ることで、大規模データ処理時の設計にも役立ちます。並列処理を行うArrays.parallelSortの存在も、性能を重視する開発では重要な選択肢となります。
プリミティブ型とラッパークラスの違いについても整理しました。用途に応じて適切な型を選択することが、パフォーマンスと柔軟性の両立につながります。特にComparatorを使う場合はラッパークラスが必須となるため、意識して使い分けることが重要です。
実践的な応用としては、複数条件によるソートやnull値の扱い、安定ソートの考え方なども理解しておくことで、より実務に近い開発が可能になります。配列のソートは単なる並び替えにとどまらず、検索や分析、データ整形の基盤となる重要な処理です。
サンプルプログラムまとめ
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
public class SummarySortExample {
public static void main(String[] args) {
Integer[] numbers = {5, 2, 9, 1, 7};
Arrays.sort(numbers);
System.out.println("昇順: " + Arrays.toString(numbers));
Arrays.sort(numbers, Collections.reverseOrder());
System.out.println("降順: " + Arrays.toString(numbers));
String[] words = {"Java", "配列", "ソート", "学習"};
Arrays.sort(words, (a, b) -> Integer.compare(a.length(), b.length()));
System.out.println("文字数順: " + Arrays.toString(words));
}
}
昇順: [1, 2, 5, 7, 9]
降順: [9, 7, 5, 2, 1]
文字数順: [Java, 配列, ソート, 学習]
生徒
配列のソートって思っていたより簡単でした。Arrays.sortを使うだけでいいんですね。
先生
そうですね。まずは基本として昇順ソートをしっかり理解することが大切です。その上でComparatorを使うと応用が広がります。
生徒
降順にしたいときにラッパークラスを使う必要があるのは少し意外でした。
先生
プリミティブ型とオブジェクト型の違いは重要なポイントです。実務でもよく使うので覚えておきましょう。
生徒
カスタムComparatorで自由に並び替えできるのは便利ですね。文字数でソートできるのは面白いです。
先生
その通りです。業務では複数条件でのソートもよく使うので、ラムダ式も含めて慣れておくと良いですよ。
生徒
多次元配列のソートも理解できました。データ分析にも使えそうです。
先生
配列のソートは基礎ですが、とても応用範囲が広い重要な技術です。しっかり身につけていきましょう。