配列ソートの全体像
Arrays.sort は配列を「その場で」昇順に並べ替える標準APIです。プリミティブ配列(int[] など)と参照型配列(String[] や任意クラスの配列)で動作や拡張性が少し異なります。基本は自然順(数値の昇順、文字列の辞書順)でソートし、必要に応じて Comparator を使って逆順や独自ルールへ切り替えます。配列は破壊的に並べ替えられるので、元の順序が必要ならコピーを作ってから使いましょう。
基本の使い方
プリミティブ配列のソート
int[] a = {5, 2, 9, 1};
java.util.Arrays.sort(a); // [1, 2, 5, 9]
Java- 特徴: 高速なクイックソート系(Dual-Pivot)で「不安定」(同値要素の相対順は保証されない)。
- 浮動小数点:
Double.compare/Float.compareに準じ、NaN は全ての値より大きく扱われます。-0.0は0.0よりも小さい順序になります。
double[] d = {0.0, -0.0, Double.NaN, 1.0};
java.util.Arrays.sort(d); // [-0.0, 0.0, 1.0, NaN]
Java参照型配列(自然順)のソート
String[] s = {"Banana", "Apple", "Cherry"};
java.util.Arrays.sort(s); // ["Apple", "Banana", "Cherry"]
Java- 特徴: 安定ソート(TimSort)。同値要素の相対順が保たれるため、複合ソートや後段処理で安心です。
- 要件: 要素型が Comparable(自然順を定義)であること。未定義なら Comparator を渡します。
範囲指定のソート
int[] a = {9, 7, 5, 3, 1};
java.util.Arrays.sort(a, 1, 4); // index 1~3 を昇順 → [9, 3, 5, 7, 1]
Java- 範囲: [from, to)(from 包括・to 排他)。境界は「0 ≤ from ≤ to ≤ length」を厳守します。
カスタム順序(Comparator)
逆順・キーでのソート
String[] s = {"Banana", "Apple", "Cherry"};
java.util.Arrays.sort(s, java.util.Comparator.reverseOrder()); // 逆順
record Product(String id, String name, int price) {}
Product[] ps = {
new Product("A","Pen",200),
new Product("B","Note",100),
new Product("C","Bag",300)
};
// 価格の昇順→名前の昇順で安定ソート
java.util.Arrays.sort(ps,
java.util.Comparator.comparingInt(Product::price)
.thenComparing(Product::name));
Java- ポイント: 参照型配列は Comparator で好きな基準に。安定ソートなので「二段階キー」も自然に決まります。
null の扱い
String[] s = {"b", null, "a"};
java.util.Arrays.sort(s, java.util.Comparator.nullsLast(java.util.Comparator.naturalOrder()));
// [ "a", "b", null ]
Java- デフォルト: 自然順や多くの Comparator は null を許さず NPE。必要なら
nullsFirst/nullsLastを組み合わせます。
比較の安全な書き方
// 悪手:引き算はオーバーフローや符号誤りの原因
// return a.price - b.price;
// 良い:compare 系を使う
return Integer.compare(a.price, b.price);
// 複数キーは thenComparing で連鎖
Java- 理由: subtract は境界で誤判定が起きます。
Integer.compare/Long.compare/Double.compareを使うのが定石。
重要ポイントの深掘り:アルゴリズム、安定性、性能
アルゴリズムの違い
- プリミティブ配列: Dual-Pivot QuickSort。平均 (O(n \log n))、不安定、実装が軽く高速。
- 参照型配列: TimSort。平均/最悪 (O(n \log n))、安定、既に部分的に整列されたデータに強い。
メモリと破壊性
- その場ソート: 配列自身が並び替えられます。元順序が必要なら
Arrays.copyOfで防御的コピーを作ってからソートします。
parallelSort の活用
int[] a = /* 大きめの配列 */;
java.util.Arrays.parallelSort(a);
Java- 用途: 非常に大きな配列で並列ソート。全てが高速化するわけではないので、計測して有効時のみ採用します。
よくある落とし穴と対策
文字列の「人に優しい順序」
- 課題:
Stringの自然順は Unicode コードポイント順。ユーザー向けにはロケール依存のCollatorを使い、配列ならArrays.sort(arr, collator)相当を行います(Listへ変換してsortするのが一般的)。
var coll = java.text.Collator.getInstance(java.util.Locale.JAPAN);
coll.setStrength(java.text.Collator.PRIMARY); // 大小・濁点を弱める
java.util.Arrays.sort(s, coll);
JavaComparator の整合性
- 注意: 非推移や不一致(equals と矛盾)な比較はソートを壊します。キーの順序付けは推移的で完全な弱順序になるように設計してください。
NaN と 0.0/-0.0
- 浮動小数点: NaN は最大扱い、
-0.0 < 0.0。この振る舞いが仕様要件と合わない場合、値の前処理(正規化)やカスタム Comparator を用意します。
破壊的であることを忘れる
- 対策: 元配列が必要ならコピーしてからソート。共有配列を勝手に並べ替えない設計にする。
例題で身につける
例 1: 基本の昇順・部分ソート
int[] a = {9, 7, 5, 3, 1};
java.util.Arrays.sort(a); // [1,3,5,7,9]
java.util.Arrays.sort(a, 1, 4); // index 1..3 を昇順 → [1,3,5,7,9] から [1,3,5,7,9](範囲のみ影響)
Java例 2: 逆順と複合キー
String[] s = {"Banana","Apple","Cherry"};
java.util.Arrays.sort(s, java.util.Comparator.reverseOrder()); // 逆順
record User(String id, String name, int age) {}
User[] users = {
new User("A","Sato",30),
new User("B","Sato",25),
new User("C","Ito",40)
};
// 名前昇順→年齢昇順
java.util.Arrays.sort(users,
java.util.Comparator.comparing(User::name)
.thenComparingInt(User::age));
Java例 3: null を後ろに、大小無視で文字列
String[] s = {"b", null, "A", "aa"};
java.util.Arrays.sort(s,
java.util.Comparator.nullsLast(
java.util.Comparator.comparing(String::toLowerCase)
)
);
// [ "A", "aa", "b", null ]
Java例 4: 浮動小数点の並びと正規化
double[] d = {0.0, -0.0, Double.NaN, 1.0};
java.util.Arrays.sort(d); // [-0.0, 0.0, 1.0, NaN]
// -0.0 を 0.0 に正規化してからソートしたい場合
for (int i = 0; i < d.length; i++) if (d[i] == 0.0) d[i] = 0.0; // -0.0 → 0.0
java.util.Arrays.sort(d);
Java例 5: 大規模データで並列ソート
int[] big = java.util.stream.IntStream.range(0, 1_000_000).map(i -> ~i).toArray();
java.util.Arrays.parallelSort(big);
Java仕上げのアドバイス(重要部分のまとめ)
配列のソートは「プリミティブ=不安定・高速」「参照型=安定・柔軟」を押さえ、用途に応じて Comparator で順序を設計する。範囲指定は [from, to) を厳守し、浮動小数点の NaN と -0.0 の扱いを理解する。人に見せる並びには Collator を使い、比較は compare 系で安全に書く。配列は破壊される前提で、必要ならコピーしてから操作する——この型が身につけば、Arrays.sort を迷いなく使いこなせます。
