- JAVA
Java配列ソート|Arrays.sortで昇順・降順・文字列を並び替え
Javaの配列ソートとは、配列の要素を小さい順(昇順)や大きい順(降順)に並び替えることで、Arrays.sort(配列) の1行で昇順にできます。 降順にするときは第2引数に Collections.reverseOrder() を渡しますが、これが使えるのは Integer[] や String[] などの参照型の配列だけで、int[] に渡すとコンパイルエラーになります。
この記事では、int[] を降順にしようとして出るコンパイルエラーを実際に再現し、3つの解決コードを実行結果つきで並べます。文字列の並び順でつまずきやすい大文字と小文字・日本語・数字の文字列("10" が "9" より前に来る)、オブジェクト配列、2次元配列、Stream での書き方まで、OpenJDK 21.0.10 で動かした出力とあわせてまとめました。
配列ソートの型別早見表
| 配列の型 | 昇順 | 降順 |
|---|---|---|
int[] などの基本型 |
Arrays.sort(a) |
昇順にしてから反転、または Integer[] に変換、または Stream |
Integer[] |
Arrays.sort(a) |
Arrays.sort(a, Collections.reverseOrder()) |
String[] |
Arrays.sort(a) |
Arrays.sort(a, Collections.reverseOrder()) |
String[](大文字小文字を区別しない) |
Arrays.sort(a, String.CASE_INSENSITIVE_ORDER) |
Arrays.sort(a, String.CASE_INSENSITIVE_ORDER.reversed()) |
String[](数字の文字列) |
Arrays.sort(a, Comparator.comparingInt(Integer::parseInt)) |
Comparator.<String>comparingInt(Integer::parseInt).reversed() |
| オブジェクト配列 | Arrays.sort(a, Comparator.comparingInt(Member::age)) |
Arrays.sort(a, Comparator.comparingInt(Member::age).reversed()) |
2次元配列 int[][] |
Arrays.sort(a, Comparator.comparingInt(r -> r[0])) |
Arrays.sort(a, (x, y) -> Integer.compare(y[0], x[0])) |
| 元の配列を残したい | Arrays.stream(a).sorted().toArray() |
boxed() と sorted(Comparator.reverseOrder()) を使う |
Arrays と Collections、Comparator はいずれも java.util パッケージにあり、使うときは import java.util.Arrays; のように import します。
Arrays.sort は元の配列そのものを並び替えます(戻り値はありません)。 元の順番も残したいときは、先に clone() でコピーしてから並び替えるか、Stream を使います。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java配列ソート|Arrays.sortで昇順・降順・文字列を並び替え」
Arrays.sortで昇順に並び替える
基本の使い方は、配列を渡すだけです。数値は小さい順、文字列は辞書順に並びます。
import java.util.Arrays;
public class Basic {
public static void main(String[] args) {
int[] nums = {5, 3, 9, 1, 7};
Arrays.sort(nums); // 昇順(小さい順)に並び替える
System.out.println("int[] : " + Arrays.toString(nums));
double[] weights = {19.8, 3.5, 120.0, 0.5};
Arrays.sort(weights);
System.out.println("double[] : " + Arrays.toString(weights));
char[] chars = {'d', 'A', 'c', 'B'};
Arrays.sort(chars);
System.out.println("char[] : " + Arrays.toString(chars));
String[] names = {"sato", "abe", "kato"};
Arrays.sort(names);
System.out.println("String[] : " + Arrays.toString(names));
// 範囲を指定:インデックス1から、4の手前まで
int[] part = {9, 8, 7, 6, 5};
Arrays.sort(part, 1, 4);
System.out.println("範囲指定 : " + Arrays.toString(part));
}
}
実行結果(OpenJDK 21.0.10、java Basic.java):
int[] : [1, 3, 5, 7, 9]
double[] : [0.5, 3.5, 19.8, 120.0]
char[] : [A, B, c, d]
String[] : [abe, kato, sato]
範囲指定 : [9, 6, 7, 8, 5]
char[] の結果が [A, B, c, d] になっているのは、文字コードの順に並ぶためです。大文字(A は65)は小文字(a は97)より番号が小さいので、先に来ます。文字列でも同じことが起きるので、後半の「文字列を並び替える」で詳しく扱います。
範囲を指定して並び替える
Arrays.sort(配列, 開始, 終了) と書くと、開始位置から「終了位置の手前」までだけを並び替えます。上の例では、インデックス1〜3の 8, 7, 6 だけが 6, 7, 8 になり、両端の9と5はそのままです。
元の配列を残して並び替える
Arrays.sort は渡した配列の中身を直接書き換えます。元の順番を残したいときは、int[] copy = original.clone(); でコピーしてから Arrays.sort(copy) を呼ぶか、Arrays.stream(original).sorted().toArray() で新しい配列を作ります。{3, 1, 2} で試したところ、どちらの方法でも元の配列は [3, 1, 2] のまま、結果は [1, 2, 3] になりました。メソッドの引数で受け取った配列を並び替えると呼び出し元の配列も変わるので、必要に応じてコピーしてから並び替えます。
int[]を降順にするとコンパイルエラーになる例
降順の書き方として広く知られているのが、第2引数に Collections.reverseOrder() を渡す方法です。これを int[] にそのまま使うと、次のようになります。
import java.util.Arrays;
import java.util.Collections;
public class DescError {
public static void main(String[] args) {
int[] nums = {5, 3, 9, 1, 7};
Arrays.sort(nums, Collections.reverseOrder());
System.out.println(Arrays.toString(nums));
}
}
実行結果(javac DescError.java、JDK 21.0.10):
DescError.java:7: error: no suitable method found for sort(int[],Comparator<Object>)
Arrays.sort(nums, Collections.reverseOrder());
^
method Arrays.<T#1>sort(T#1[],Comparator<? super T#1>) is not applicable
(inference variable T#1 has incompatible bounds
equality constraints: int
upper bounds: Object)
method Arrays.<T#2>sort(T#2[],int,int,Comparator<? super T#2>) is not applicable
(cannot infer type-variable(s) T#2
(actual and formal argument lists differ in length))
where T#1,T#2 are type-variables:
T#1 extends Object declared in method <T#1>sort(T#1[],Comparator<? super T#1>)
T#2 extends Object declared in method <T#2>sort(T#2[],int,int,Comparator<? super T#2>)
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
先頭の no suitable method found for sort(int[],Comparator<Object>) は、「int[] と Comparator を受け取る sort メソッドが見つかりません」という意味です。
理由はメッセージの中ほどに書かれています。Comparator を受け取る sort は T[](T は何かのクラス)を引数にとるメソッドで、upper bounds: Object とあるとおり、T にはクラス(参照型)しか当てはめられません。 int は基本型でクラスではないため、equality constraints: int と合わず、当てはまるメソッドがないと判断されます。
| 配列の型 | Arrays.sort(a) |
Arrays.sort(a, Comparator) |
|---|---|---|
int[]、long[]、double[]、char[] などの基本型 |
使える | 使えない(コンパイルエラー) |
Integer[]、String[]、自作クラスの配列 |
使える(Comparable を実装している場合) | 使える |
int[]を降順にする3つの解決コード
int[] を降順にする方法を3つ並べます。どれも同じ結果になります。
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
public class DescFix {
public static void main(String[] args) {
int[] nums = {5, 3, 9, 1, 7};
// 解決1:Integer[] に変換してから Collections.reverseOrder() を渡す
Integer[] boxed = Arrays.stream(nums).boxed().toArray(Integer[]::new);
Arrays.sort(boxed, Collections.reverseOrder());
System.out.println("解決1 Integer[] に変換 : " + Arrays.toString(boxed));
// 解決2:Stream で降順に並び替えて、int[] に戻す
int[] desc2 = Arrays.stream(nums)
.boxed()
.sorted(Comparator.reverseOrder())
.mapToInt(Integer::intValue)
.toArray();
System.out.println("解決2 Stream : " + Arrays.toString(desc2));
// 解決3:昇順に並び替えてから、前後を入れ替えて反転する
int[] desc3 = nums.clone();
Arrays.sort(desc3);
for (int i = 0, j = desc3.length - 1; i < j; i++, j--) {
int tmp = desc3[i];
desc3[i] = desc3[j];
desc3[j] = tmp;
}
System.out.println("解決3 昇順のあと反転 : " + Arrays.toString(desc3));
System.out.println("元の配列 : " + Arrays.toString(nums));
}
}
実行結果:
解決1 Integer[] に変換 : [9, 7, 5, 3, 1]
解決2 Stream : [9, 7, 5, 3, 1]
解決3 昇順のあと反転 : [9, 7, 5, 3, 1]
元の配列 : [5, 3, 9, 1, 7]
| 方法 | 結果の型 | 元の配列 | 向いている場面 |
|---|---|---|---|
解決1:Integer[] に変換 |
Integer[] |
変わらない | 以降も Integer[] のまま扱う、Comparator を使い回したい |
| 解決2:Stream | int[] |
変わらない | 1つの式で書きたい、絞り込みや件数制限を続けたい |
| 解決3:昇順のあと反転 | int[] |
コピーすれば変わらない | 変換を挟まずに int[] のまま処理したい |
解決1:Integer[]に変換する
Arrays.stream(nums).boxed().toArray(Integer[]::new) で、int[] を Integer[] に変換できます。boxed() は int を Integer に包む(ボクシングする)処理です。Integer[] になれば Collections.reverseOrder() を渡せます。
最初から Integer[] で配列を作っている場合は、変換なしでそのまま降順にできます。降順の書き方はいくつかあり、結果は同じです。
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
public class IntegerDesc {
public static void main(String[] args) {
Integer[] a = {5, 3, 9, 1, 7};
Arrays.sort(a, Collections.reverseOrder());
System.out.println("Collections.reverseOrder() : " + Arrays.toString(a));
Integer[] b = {5, 3, 9, 1, 7};
Arrays.sort(b, Comparator.reverseOrder());
System.out.println("Comparator.reverseOrder() : " + Arrays.toString(b));
Integer[] c = {5, 3, 9, 1, 7};
Arrays.sort(c, (x, y) -> Integer.compare(y, x));
System.out.println("Integer.compare(y, x) : " + Arrays.toString(c));
// 引き算で比べると、差が int の範囲を超えたときに順番が変わる
Integer[] d = {Integer.MIN_VALUE, 1, 5};
Arrays.sort(d, (x, y) -> y - x);
System.out.println("y - x で比較 : " + Arrays.toString(d));
Arrays.sort(d, (x, y) -> Integer.compare(y, x));
System.out.println("Integer.compare で比較 : " + Arrays.toString(d));
}
}
実行結果:
Collections.reverseOrder() : [9, 7, 5, 3, 1]
Comparator.reverseOrder() : [9, 7, 5, 3, 1]
Integer.compare(y, x) : [9, 7, 5, 3, 1]
y - x で比較 : [-2147483648, 5, 1]
Integer.compare で比較 : [5, 1, -2147483648]
後半の2行は、比べ方を自分で書くときの注意点です。(x, y) -> y - x のように引き算で比べると、差が int の範囲を超えたときに並び順が変わります。 上の例では、いちばん小さい Integer.MIN_VALUE が降順の先頭に来ています。Integer.compare(y, x) を使うと、値の大きさにかかわらず正しい順番になります。
解決2と解決3の補足
解決2は、boxed() で Integer に包み、sorted(Comparator.reverseOrder()) で降順にしてから、mapToInt(Integer::intValue) で int に戻しています。結果が int[] で得られ、元の配列も変わらないのが利点です。
解決3は変換を挟まないので、long[] や double[] でも型を書き換えるだけで同じ形で使えます。配列用の反転メソッドは標準ライブラリにないため、上のコードの for 文をそのまま使ってください。
文字列を並び替えるときの3つの注意点
String[] を Arrays.sort に渡すと、文字コード(Unicode の番号)の小さい順に並びます。英小文字だけ、ひらがなだけなら期待どおりの辞書順ですが、種類が混ざると意外な順番になります。
大文字と小文字が混ざる場合
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
public class StrCase {
public static void main(String[] args) {
String[] words = {"banana", "Apple", "cherry", "apple", "Banana"};
String[] w1 = words.clone();
Arrays.sort(w1);
System.out.println("そのまま(昇順) : " + Arrays.toString(w1));
String[] w2 = words.clone();
Arrays.sort(w2, Collections.reverseOrder());
System.out.println("降順 : " + Arrays.toString(w2));
String[] w3 = words.clone();
Arrays.sort(w3, String.CASE_INSENSITIVE_ORDER);
System.out.println("大文字小文字を区別しない : " + Arrays.toString(w3));
String[] w4 = words.clone();
Arrays.sort(w4, String.CASE_INSENSITIVE_ORDER.thenComparing(Comparator.naturalOrder()));
System.out.println("区別せず、同じなら大文字先: " + Arrays.toString(w4));
System.out.println("文字コード A=" + (int) 'A' + " Z=" + (int) 'Z' + " a=" + (int) 'a');
}
}
実行結果:
そのまま(昇順) : [Apple, Banana, apple, banana, cherry]
降順 : [cherry, banana, apple, Banana, Apple]
大文字小文字を区別しない : [Apple, apple, banana, Banana, cherry]
区別せず、同じなら大文字先: [Apple, apple, Banana, banana, cherry]
文字コード A=65 Z=90 a=97
そのまま並べると、大文字で始まる Apple・Banana が、小文字で始まる apple より前に来ます。大文字の A〜Z は65〜90、小文字の a は97からで、大文字のほうが番号が小さいためです。
| やりたいこと | 書き方 |
|---|---|
| 大文字と小文字を区別せずに並べる | Arrays.sort(a, String.CASE_INSENSITIVE_ORDER) |
| 区別せずに並べ、同じつづりは大文字を先にする | String.CASE_INSENSITIVE_ORDER.thenComparing(Comparator.naturalOrder()) |
| 降順にする | Arrays.sort(a, Collections.reverseOrder()) |
String.CASE_INSENSITIVE_ORDER だけを使った結果では、banana と Banana が元の配列に出てきた順のまま並んでいます。順番をいつも同じにしたいときは、thenComparing で2つ目の基準を足します。
数字の文字列は"10"が"9"より前に来る
import java.util.Arrays;
import java.util.Comparator;
public class StrNum {
public static void main(String[] args) {
String[] nums = {"10", "9", "100", "2"};
String[] n1 = nums.clone();
Arrays.sort(n1);
System.out.println("そのまま : " + Arrays.toString(n1));
String[] n2 = nums.clone();
Arrays.sort(n2, Comparator.comparingInt(Integer::parseInt));
System.out.println("数値として比べる : " + Arrays.toString(n2));
String[] files = {"file10.txt", "file9.txt", "file100.txt", "file2.txt"};
String[] f1 = files.clone();
Arrays.sort(f1);
System.out.println("そのまま : " + Arrays.toString(f1));
// 数字以外(\D)を取り除いて、数字の部分だけで比べる
String[] f2 = files.clone();
Arrays.sort(f2, Comparator.comparingInt(s -> Integer.parseInt(s.replaceAll("\\D", ""))));
System.out.println("数字の部分で比べる: " + Arrays.toString(f2));
String[] len = {"ccc", "a", "bb", "dd"};
Arrays.sort(len, Comparator.comparingInt(String::length));
System.out.println("文字数の短い順 : " + Arrays.toString(len));
}
}
実行結果:
そのまま : [10, 100, 2, 9]
数値として比べる : [2, 9, 10, 100]
そのまま : [file10.txt, file100.txt, file2.txt, file9.txt]
数字の部分で比べる: [file2.txt, file9.txt, file10.txt, file100.txt]
文字数の短い順 : [a, bb, dd, ccc]
文字列の比較は、先頭の文字から1文字ずつ行われます。"10" と "9" では、最初の文字 1 と 9 を比べた時点で 1 のほうが小さいと決まるため、"10" が前に来ます。"100" が "2" より前に来るのも同じ理由です。
| データ | 直し方 |
|---|---|
| 数字だけの文字列 | Comparator.comparingInt(Integer::parseInt) で数値として比べる |
file10.txt のように数字が含まれる |
数字の部分を取り出して数値として比べる |
| けた数をそろえられる | 001、010、100 のように0で埋めておくと、そのままでも数値の順になる |
| 文字数で並べたい | Comparator.comparingInt(String::length) |
数字以外の文字列が混ざっていると Integer.parseInt が NumberFormatException を出すので、数字だけであることがわかっているデータに使います。
日本語は文字コード順になる
import java.text.Collator;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Locale;
public class StrJp {
record Person(String name, String kana) {}
public static void main(String[] args) {
// (1) ひらがな同士は五十音順になる
String[] kana = {"さとう", "あべ", "やまだ", "かとう", "いとう"};
Arrays.sort(kana);
System.out.println("(1) ひらがな : " + Arrays.toString(kana));
// (2) 種類が混ざると、文字コードの順になる
String[] mixed = {"山田", "佐藤", "あべ", "カトウ", "伊藤", "abc", "いとう"};
String[] m1 = mixed.clone();
Arrays.sort(m1);
System.out.println("(2) 混在 そのまま : " + Arrays.toString(m1));
// (3) Collator を使っても、漢字は読みの順にはならない
String[] m2 = mixed.clone();
Arrays.sort(m2, Collator.getInstance(Locale.JAPANESE));
System.out.println("(3) 混在 Collator : " + Arrays.toString(m2));
// (4) 濁点・カタカナが混ざるとき
String[] daku = {"ばなな", "はな", "ぱん", "ハナ", "はなび"};
String[] d1 = daku.clone();
Arrays.sort(d1);
System.out.println("(4) 濁点 そのまま : " + Arrays.toString(d1));
String[] d2 = daku.clone();
Arrays.sort(d2, Collator.getInstance(Locale.JAPANESE));
System.out.println(" 濁点 Collator : " + Arrays.toString(d2));
// (5) 漢字の名前を読みの順にするには、読みがなを別に持つ
Person[] people = {
new Person("山田", "やまだ"), new Person("佐藤", "さとう"), new Person("伊藤", "いとう")
};
Arrays.sort(people, Comparator.comparing(Person::kana));
StringBuilder sb = new StringBuilder();
for (Person p : people) {
sb.append(p.name()).append(" ");
}
System.out.println("(5) 読みがなで比べる: " + sb.toString().trim());
}
}
実行結果:
(1) ひらがな : [あべ, いとう, かとう, さとう, やまだ]
(2) 混在 そのまま : [abc, あべ, いとう, カトウ, 伊藤, 佐藤, 山田]
(3) 混在 Collator : [abc, あべ, いとう, カトウ, 伊藤, 佐藤, 山田]
(4) 濁点 そのまま : [はな, はなび, ばなな, ぱん, ハナ]
濁点 Collator : [はな, ハナ, ばなな, はなび, ぱん]
(5) 読みがなで比べる: 伊藤 佐藤 山田
実行結果から、次のことがわかります。
- (1) ひらがなだけなら五十音順になります。ひらがなは文字コードが五十音の順に並んでいるためです。
- (2) 種類が混ざると、英字 → ひらがな → カタカナ → 漢字の順になります。漢字は読みではなく文字コードの順なので、
伊藤・佐藤・山田の並びは読みの五十音順とは関係がありません(今回はたまたま五十音順と同じ並びです)。 - (3)
Collator(言語に合わせた比較を行うクラス)を使っても、漢字は読みの順にはなりません。 漢字の文字そのものには読みの情報が含まれていないためです。 - (4) 濁点やカタカナが混ざる場合は、
Collatorで並び順が変わります。 そのままではばなな・ぱんがひらがなの最後、カタカナのハナがさらに後ろに来ますが、Collatorを使うとはな・ハナが隣り合い、辞書に近い順になりました。 - (5) 漢字の名前を読みの順に並べたいときは、読みがなを別に持たせて、その値で比べます。
| データ | 並べ方 |
|---|---|
| ひらがなだけ、カタカナだけ | Arrays.sort(a) |
| ひらがな・カタカナ・濁点が混ざる | Arrays.sort(a, Collator.getInstance(Locale.JAPANESE)) |
| 漢字を読みの順に並べたい | 読みがなのフィールドを用意し、Comparator.comparing で比べる |
Collator の並び順は Java のバージョンや実行環境の設定で変わる可能性があります。上の結果は OpenJDK 21.0.10 のものです。
nullが含まれる場合
配列に null が入っていると、比べる途中で NullPointerException になります。Comparator.nullsLast か Comparator.nullsFirst で、null の置き場所を決められます。{"b", null, "a"} を並び替えた結果は次のとおりです。
| 書き方 | 実行結果 |
|---|---|
Arrays.sort(a) |
NullPointerException |
Arrays.sort(a, Comparator.nullsLast(Comparator.naturalOrder())) |
[a, b, null] |
Arrays.sort(a, Comparator.nullsFirst(Comparator.naturalOrder())) |
[null, a, b] |
オブジェクト配列を並び替える
自作クラスや record の配列は、「どの項目で比べるか」を Comparator で渡します。
import java.util.Arrays;
import java.util.Comparator;
public class Obj {
record Member(String name, int age, String team) {}
static void print(String label, Member[] members) {
StringBuilder sb = new StringBuilder(label + " : ");
for (Member m : members) {
sb.append(m.name()).append("(").append(m.age()).append(",").append(m.team()).append(") ");
}
System.out.println(sb.toString().trim());
}
public static void main(String[] args) {
Member[] members = {
new Member("佐藤", 28, "B"),
new Member("鈴木", 35, "A"),
new Member("高橋", 28, "A"),
new Member("田中", 41, "B"),
};
Member[] m1 = members.clone();
Arrays.sort(m1, Comparator.comparingInt(Member::age));
print("年齢の昇順 ", m1);
Member[] m2 = members.clone();
Arrays.sort(m2, Comparator.comparingInt(Member::age).reversed());
print("年齢の降順 ", m2);
Member[] m3 = members.clone();
Arrays.sort(m3, Comparator.comparing(Member::team)
.thenComparing(Member::age, Comparator.reverseOrder()));
print("チーム昇順→年齢降順", m3);
Member[] m4 = members.clone();
Arrays.sort(m4, (a, b) -> Integer.compare(a.age(), b.age()));
print("ラムダ式で年齢の昇順", m4);
// Comparator を渡さないと、比べ方がわからず例外になる
try {
Arrays.sort(members.clone());
} catch (ClassCastException e) {
System.out.println("Comparator なし : ClassCastException");
}
}
}
実行結果:
年齢の昇順 : 佐藤(28,B) 高橋(28,A) 鈴木(35,A) 田中(41,B)
年齢の降順 : 田中(41,B) 鈴木(35,A) 佐藤(28,B) 高橋(28,A)
チーム昇順→年齢降順 : 鈴木(35,A) 高橋(28,A) 田中(41,B) 佐藤(28,B)
ラムダ式で年齢の昇順 : 佐藤(28,B) 高橋(28,A) 鈴木(35,A) 田中(41,B)
Comparator なし : ClassCastException
| やりたいこと | 書き方 |
|---|---|
| int の項目で昇順 | Comparator.comparingInt(Member::age) |
| 降順にする | 末尾に .reversed() を付ける |
| 文字列などの項目で昇順 | Comparator.comparing(Member::team) |
| 2つ目の基準を足す | .thenComparing(...) を続ける |
| 2つ目の基準だけ降順 | .thenComparing(Member::age, Comparator.reverseOrder()) |
最後の行は、Comparator を渡さずに Arrays.sort(members) を呼んだ結果です。比べ方が決まっていないクラスの配列では、実行時に ClassCastException になります。メッセージは「Member を Comparable に変換できません」という内容でした。
同じ値の要素は元の順番が保たれる
年齢が同じ人が複数いる配列を、年齢で並び替えてみます。
import java.util.Arrays;
import java.util.Comparator;
public class Stable {
record Member(String name, int age) {}
public static void main(String[] args) {
Member[] members = {
new Member("佐藤", 28), new Member("鈴木", 35), new Member("高橋", 28), new Member("田中", 35)
};
Arrays.sort(members, Comparator.comparingInt(Member::age));
for (Member m : members) {
System.out.println(m.age() + " " + m.name());
}
}
}
実行結果:
28 佐藤
28 高橋
35 鈴木
35 田中
28歳の2人は「佐藤 → 高橋」、35歳の2人は「鈴木 → 田中」と、元の配列に出てきた順のままです。Java 21 の公式APIドキュメント(Arrays クラス)を開いて確認したところ、オブジェクト配列の sort は安定(同じ値の要素の順番を入れ替えない)であることが保証されています。先に第2の基準で並べ、次に第1の基準で並べる、という2段階の並び替えができるのは、この性質があるためです。
クラスに「ふつうの順番」を持たせる(Comparable)
いつも同じ基準で並べるクラスなら、Comparable を実装して compareTo に比べ方を書いておくと、Comparator なしで Arrays.sort(配列) を呼べます。
import java.util.Arrays;
import java.util.Collections;
public class Cmp {
static class Task implements Comparable<Task> {
final String title;
final int priority;
Task(String title, int priority) {
this.title = title;
this.priority = priority;
}
// 「ふつうの順番」を優先度の小さい順と決める
@Override
public int compareTo(Task other) {
return Integer.compare(this.priority, other.priority);
}
@Override
public String toString() {
return title + "(" + priority + ")";
}
}
public static void main(String[] args) {
Task[] tasks = { new Task("資料作成", 2), new Task("レビュー", 1), new Task("会議準備", 3) };
Arrays.sort(tasks); // Comparator なしで並び替えできる
System.out.println("昇順 : " + Arrays.toString(tasks));
Arrays.sort(tasks, Collections.reverseOrder()); // 降順も書ける
System.out.println("降順 : " + Arrays.toString(tasks));
}
}
実行結果:
昇順 : [レビュー(1), 資料作成(2), 会議準備(3)]
降順 : [会議準備(3), 資料作成(2), レビュー(1)]
基準が1つに決まるなら Comparable、場面ごとに基準を変えたいなら Comparator と使い分けます。
2次元配列を並び替える
2次元配列は「配列の配列」なので、行をひとかたまりとして、どの列で比べるかを Comparator で渡します。
import java.util.Arrays;
import java.util.Comparator;
public class TwoDim {
public static void main(String[] args) {
// 各行が {番号, 点数}
int[][] rows = { {3, 50}, {1, 80}, {2, 50}, {1, 20} };
int[][] r1 = rows.clone();
Arrays.sort(r1, Comparator.comparingInt(r -> r[0]));
System.out.println("0列目の昇順 : " + Arrays.deepToString(r1));
int[][] r2 = rows.clone();
Arrays.sort(r2, (a, b) -> Integer.compare(b[1], a[1]));
System.out.println("1列目の降順 : " + Arrays.deepToString(r2));
int[][] r3 = rows.clone();
Arrays.sort(r3, Comparator.<int[]>comparingInt(r -> r[1]).reversed()
.thenComparingInt(r -> r[0]));
System.out.println("1列目降順→0列目昇順 : " + Arrays.deepToString(r3));
// 行の中身をそれぞれ昇順にする
int[][] r4 = { {3, 1, 2}, {9, 7, 8} };
for (int[] row : r4) {
Arrays.sort(row);
}
System.out.println("各行の中を昇順 : " + Arrays.deepToString(r4));
// Comparator を渡さないと例外になる
try {
Arrays.sort(new int[][] { {2}, {1} });
} catch (ClassCastException e) {
System.out.println("Comparator なし : ClassCastException");
}
// String の2次元配列を、1列目の数値で並び替える
String[][] table = { {"鈴木", "35"}, {"佐藤", "28"}, {"田中", "41"} };
Arrays.sort(table, Comparator.comparingInt(r -> Integer.parseInt(r[1])));
System.out.println("String[][] 数値で昇順: " + Arrays.deepToString(table));
}
}
実行結果:
0列目の昇順 : [[1, 80], [1, 20], [2, 50], [3, 50]]
1列目の降順 : [[1, 80], [3, 50], [2, 50], [1, 20]]
1列目降順→0列目昇順 : [[1, 80], [2, 50], [3, 50], [1, 20]]
各行の中を昇順 : [[1, 2, 3], [7, 8, 9]]
Comparator なし : ClassCastException
String[][] 数値で昇順: [[佐藤, 28], [鈴木, 35], [田中, 41]]
| やりたいこと | 書き方 |
|---|---|
| 0列目の昇順で行を並べる | Arrays.sort(a, Comparator.comparingInt(r -> r[0])) |
| 1列目の降順で行を並べる | Arrays.sort(a, (x, y) -> Integer.compare(y[1], x[1])) |
| 複数の列で並べる | Comparator.<int[]>comparingInt(r -> r[1]).reversed().thenComparingInt(r -> r[0]) |
| 各行の中身を並べる | for 文で1行ずつ Arrays.sort(row) |
reversed() や thenComparingInt を続けるときは、Comparator.<int[]>comparingInt(...) のように型を書き添えます。メソッドをつなげるとラムダ式の引数 r の型を推論できなくなるためです。
Comparator を渡さずに Arrays.sort(2次元配列) を呼ぶと、行(int[])同士の比べ方が決まっていないため、ClassCastException になります。
行を並び替えても、行の配列そのものはコピーされません。上のコードの rows.clone() は外側の配列だけを写しているので、行の順番は独立しますが、行の中身は元と共有されています。
Streamで並び替える
Stream を使うと、元の配列を変えずに、並び替えた新しい配列を作れます。 並び替えの前後に、絞り込みや件数の制限を続けて書けるのも特徴です。
import java.util.Arrays;
import java.util.Comparator;
public class StreamSort {
public static void main(String[] args) {
int[] nums = {5, 3, 9, 1, 7};
int[] asc = Arrays.stream(nums).sorted().toArray();
System.out.println("昇順(新しい配列) : " + Arrays.toString(asc));
System.out.println("元の配列 : " + Arrays.toString(nums));
String[] words = {"banana", "Apple", "cherry"};
String[] sorted = Arrays.stream(words)
.sorted(String.CASE_INSENSITIVE_ORDER)
.toArray(String[]::new);
System.out.println("文字列 : " + Arrays.toString(sorted));
int[] top3 = Arrays.stream(nums)
.boxed()
.sorted(Comparator.reverseOrder())
.limit(3)
.mapToInt(Integer::intValue)
.toArray();
System.out.println("大きい順に上位3件 : " + Arrays.toString(top3));
int[] unique = Arrays.stream(new int[] {3, 1, 3, 2, 1}).distinct().sorted().toArray();
System.out.println("重複を除いて昇順 : " + Arrays.toString(unique));
}
}
実行結果:
昇順(新しい配列) : [1, 3, 5, 7, 9]
元の配列 : [5, 3, 9, 1, 7]
文字列 : [Apple, banana, cherry]
大きい順に上位3件 : [9, 7, 5]
重複を除いて昇順 : [1, 2, 3]
| 項目 | Arrays.sort |
Stream の sorted |
|---|---|---|
| 元の配列 | 書き換わる | 変わらない |
| 結果 | 同じ配列 | 新しい配列 |
int[] の降順 |
直接は書けない | boxed() を挟めば書ける |
| 続けて書ける処理 | なし | filter、limit、distinct、map など |
| 向いている場面 | その場で並び替えれば足りる | 元を残したい、加工を続けたい |
大きな配列とparallelSort
Arrays.parallelSort は、配列を分割して複数のスレッドで並び替えるメソッドで、書き方は Arrays.sort と同じです。Java 21 の公式APIドキュメント(Arrays クラス)を開いて確認したところ、配列が一定の大きさより小さい場合は Arrays.sort と同じ方法で処理されると説明されています。要素数の多い配列で効果が出るメソッドですが、速度の比較は今回は未実行です。
同じドキュメントでは、基本型の配列の sort は Dual-Pivot Quicksort、オブジェクト配列の sort は安定なマージソート系のアルゴリズムと説明されています。どちらも要素数 n に対して O(n log n) の性能です。
つまずきやすい点と直し方
| 状況 | 直す前 | 直した後 |
|---|---|---|
int[] の降順でコンパイルエラー |
Arrays.sort(nums, Collections.reverseOrder()) |
Integer[] に変換、Stream、昇順のあと反転のいずれか |
| 結果を変数で受けようとした | int[] s = Arrays.sort(nums); |
Arrays.sort(nums); のあと nums を使う |
| 元の配列まで並び替わった | Arrays.sort(original) |
int[] copy = original.clone(); のあと Arrays.sort(copy) |
| 大文字が先に来る | Arrays.sort(words) |
Arrays.sort(words, String.CASE_INSENSITIVE_ORDER) |
"10" が "9" より前に来る |
Arrays.sort(nums) |
Arrays.sort(nums, Comparator.comparingInt(Integer::parseInt)) |
| 自作クラスで ClassCastException | Arrays.sort(members) |
Comparator を渡す、または Comparable を実装する |
| null 入りの配列で NullPointerException | Arrays.sort(a) |
Arrays.sort(a, Comparator.nullsLast(Comparator.naturalOrder())) |
| 大きな値で並び順が変わる | (x, y) -> y - x |
(x, y) -> Integer.compare(y, x) |
確認用のチェックリスト
- 配列の型が基本型(
int[]など)か参照型(Integer[]・String[]など)かを確認している - 降順にするとき、基本型なら3つの解決コードのどれかを使っている
- 元の配列を残す必要があるかを確認し、必要ならコピーか Stream を使っている
- 文字列に大文字と小文字、数字、日本語が混ざるかを確認している
- 自作クラスの配列には Comparator か Comparable を用意している
- 比べ方を自分で書くとき、引き算ではなく
Integer.compareを使っている
よくある質問
Q. int[]を降順にするいちばん簡単な方法はどれですか?
A. 結果を int[] のまま得たいなら、Stream で boxed()・sorted(Comparator.reverseOrder())・mapToInt(Integer::intValue)・toArray() と続ける方法が1つの式で書けます。その場で並び替えたい場合は、Arrays.sort で昇順にしてから前後を入れ替える方法が合います。
Q. Collections.reverseOrder()とComparator.reverseOrder()の違いは何ですか?
A. どちらも「ふつうの順番の逆」を表す Comparator を返し、この記事の実行結果でも同じ並びになりました。Comparator.reverseOrder() は Java 8 で加わった書き方で、Comparator.comparing などと同じクラスにそろえて書けます。どちらを使っても結果は変わりません。
Q. Arrays.sortは元の配列を変更しますか?
A. 変更します。Arrays.sort は渡した配列の中身を直接並び替え、戻り値はありません。元の順番を残したい場合は、clone() でコピーしてから並び替えるか、Arrays.stream(a).sorted().toArray() で新しい配列を作ります。
まとめ
- 昇順は
Arrays.sort(配列)の1行。元の配列そのものが並び替わる int[]にCollections.reverseOrder()を渡すとコンパイルエラーになる。Comparator を使えるのは参照型の配列だけint[]の降順は「Integer[]に変換」「Stream」「昇順のあと反転」の3つから選ぶ- 文字列は文字コード順に並ぶ。大文字は小文字より前、
"10"は"9"より前に来る - 日本語の漢字は読みの順にならないので、読みがなを別に持って比べる
- オブジェクト配列と2次元配列は Comparator で「どの項目・どの列で比べるか」を渡す
- 元の配列を残したいときは
clone()してから並び替えるか、Stream のsortedを使う
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java配列ソート|Arrays.sortで昇順・降順・文字列を並び替え」
株式会社RJCでは、Javaの配列ソートのような基礎を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。