JavaでListをソートする方法まとめ|sort・Comparator・複数条件・null対応まで解説

目次

1. この記事で分かること(java list sort の最短解)

Javaで List を扱っていると、「このリストを並び替えたい」 という場面は非常によく出てきます。
そして多くの人が最初に悩むのが、

  • どのメソッドを使えばいいのか
  • Collections.sortlist.sort の違いは何か
  • オブジェクトのListはどうやって並び替えるのか

といった点です。

この記事では、そうした迷いを最短で解消できるように、結論から先に示し、その後で理由や応用を丁寧に解説していきます。

1.1 結論:Listのソートはこの形を覚えればOK

まずは最もよく使う、基本形を見てください。

list.sort(Comparator.naturalOrder());

これは、
Listを昇順(小さい順・アルファベット順)に並び替える最短コードです。

逆に、降順にしたい場合は次のように書きます。

list.sort(Comparator.reverseOrder());

この2つを覚えておけば、
StringIntegerLocalDate など、自然な順序を持つ型のListはすぐにソートできます。

1.2 オブジェクトのListは「キー指定」でソートする

実務では、次のような オブジェクトのList を並び替えることが圧倒的に多くなります。

class Person {
    private String name;
    private int age;

    // getter省略
}

この場合は、どの項目(フィールド)で並び替えるかを指定します。

list.sort(Comparator.comparing(Person::getAge));

この1行で、

  • Person のListを
  • age(年齢)を基準に
  • 昇順でソート

できます。

「Comparatorは難しい」と感じる人も多いですが、Comparator.comparing(キー) と覚えれば十分です。

1.3 この記事で扱う範囲

この記事では、次の内容を初心者にも分かる順番で解説します。

  • list.sortCollections.sort の違い
  • Comparator の基本的な使い方
  • 複数条件でのソート(例:年齢 → 名前)
  • 昇順・降順を組み合わせる方法
  • null が含まれる場合の安全な書き方
  • stream().sorted() を使う場合との違い

単なるコードの暗記ではなく、
「なぜこの書き方をするのか」 が分かることを目標にしています。

1.4 対象読者

この記事は、次のような方を想定しています。

  • Javaの基本文法(クラス・メソッド)は理解している
  • ListArrayList は使ったことがある
  • でも、ソートになると毎回調べ直している

逆に、
Javaをまったく触ったことがない完全初心者向けではありませんが、
初めてListのソートを学ぶ人でも理解できるように解説しています。

2. Listをソートする代表的な3手段(どれを使う?)

Javaで List をソートする方法は、実は1つではありません
代表的な方法は次の3つです。

  • list.sort(Comparator)
  • Collections.sort(list)
  • stream().sorted()

それぞれ役割や性質が少しずつ異なります。
ここで違いを整理しておくと、後のセクションが一気に理解しやすくなります。

2.1 list.sort(Comparator):現在の基本形(最もおすすめ)

Java 8以降で最も自然で、現在の標準的な書き方がこれです。

list.sort(Comparator.naturalOrder());

特徴

  • List インターフェースに直接定義されている
  • 処理がシンプルで読みやすい
  • 元のList自体が並び替えられる(破壊的)

オブジェクトのListも同様です。

list.sort(Comparator.comparing(Person::getAge));

いつ使うべきか

  • 元のListを並び替えて問題ない場合
  • 単純に「このListをソートしたい」場合

👉 迷ったらまず list.sort を選べばOK です。

2.2 Collections.sort(list):古いが今も見かける書き方

次のようなコードを、書籍や古い記事で見たことがあるかもしれません。

Collections.sort(list);

または、Comparatorを指定する形です。

Collections.sort(list, Comparator.reverseOrder());

特徴

  • Java 7以前から存在する方法
  • 内部的には list.sort とほぼ同じ動作
  • こちらも破壊的にソートされる

なぜ今はあまり使われないのか

  • Java 8で List.sort が追加されたため
  • 「Listの操作なのに、なぜCollections?」という違和感がある

そのため、新しく書くコードでは list.sort を使うのが一般的です。
ただし、既存コードを読むための知識としては重要なので、違いは覚えておきましょう。

2.3 stream().sorted():非破壊でソートしたい場合

3つ目は、Stream API を使う方法です。

List<Integer> sortedList =
    list.stream()
        .sorted()
        .collect(Collectors.toList());

Comparatorを指定することもできます。

List<Person> sortedList =
    list.stream()
        .sorted(Comparator.comparing(Person::getAge))
        .collect(Collectors.toList());

特徴

  • 元のListは変更されない(非破壊)
  • ソート結果は「新しいList」として取得
  • filter や map と組み合わせやすい

いつ使うべきか

  • 元のListを残しておきたい場合
  • データ加工の流れの中でソートしたい場合

ただし、単純なソートだけであれば、

list.sort(...)

の方が コード量も少なく、意図が明確 です。

2.4 どれを選ぶべきか(判断基準まとめ)

目的おすすめ
Listをそのまま並び替えたいlist.sort
古いコードを理解したいCollections.sort
元のListを変更したくないstream().sorted()

まずは list.sort を基本形として身につける ことが大切です。

3. 基本:昇順・降順のソート

ここからは、java list sort で最も使用頻度の高い 「昇順」「降順」 の書き方を整理します。
まずは プリミティブ型ラッパーやStringなど、基本的な型 を対象に考えていきましょう。

3.1 昇順(自然順序)でソートする

Javaでは、多くの型が 自然順序(natural order) を持っています。

  • 数値 → 小さい順
  • 文字列 → アルファベット順(辞書順)
  • 日付 → 古い順

これらは、次の1行でソートできます。

list.sort(Comparator.naturalOrder());

例:数値のList

List<Integer> numbers = Arrays.asList(5, 1, 3, 2);
numbers.sort(Comparator.naturalOrder());

結果:

[1, 2, 3, 5]

例:文字列のList

List<String> names = Arrays.asList("Tom", "Alice", "Bob");
names.sort(Comparator.naturalOrder());

結果:

[Alice, Bob, Tom]

👉 昇順で並び替えたいだけなら、この書き方が最短です。

3.2 降順でソートする

昇順とは逆に、大きい順・逆アルファベット順にしたい場合は
Comparator.reverseOrder() を使います。

list.sort(Comparator.reverseOrder());

例:数値を降順にする

List<Integer> numbers = Arrays.asList(5, 1, 3, 2);
numbers.sort(Comparator.reverseOrder());

結果:

[5, 3, 2, 1]

例:文字列を降順にする

List<String> names = Arrays.asList("Tom", "Alice", "Bob");
names.sort(Comparator.reverseOrder());

結果:

[Tom, Bob, Alice]

3.3 Comparatorを省略できるケース

次のように、Comparatorを書かずにソートできる場合もあります。

Collections.sort(list);

または、

list.sort(null);

これは、

  • Listの要素が Comparable を実装している
  • 自然順序でソートする

という条件を満たす場合に限られます。

ただし、可読性や意図の明確さの点から、
実務では次の書き方が好まれます。

list.sort(Comparator.naturalOrder());

3.4 よくある勘違い:昇順・降順は「Comparatorの役割」

初心者がよく混乱するポイントとして、

昇順・降順は sort の引数で決まる

という誤解があります。

実際には、

  • sort は「並び替える」だけ
  • どう並び替えるかは Comparator が決める

という役割分担です。

この考え方を理解しておくと、
次の オブジェクトのソート複数条件のソート が自然につながります。

4. オブジェクトListのソート:Comparatorが本題

実務で List をソートする場面の多くは、
IntegerString ではなく、独自クラス(オブジェクト)のList です。

このセクションでは、Comparator を使って
「どの項目で、どの順番に並び替えるか」 を指定する方法を、基礎から解説します。

4.1 Comparatorとは何か

Comparator は一言で言うと、

2つの要素を比較し、どちらを先に並べるかを決める仕組み

です。

内部的には、次のような考え方で動いています。

  • 負の値 → 左の要素を先にする
  • 0 → 同じ順序
  • 正の値 → 右の要素を先にする

ただし、このルールを自分で実装する必要はほとんどありません
Java 8以降は、分かりやすい補助メソッドが用意されています。

4.2 Comparator.comparing でキー指定ソート

最もよく使うのが Comparator.comparing です。

例として、次のクラスを考えます。

class Person {
    private String name;
    private int age;

    // コンストラクタ・getter省略
}

この Person のListを 年齢順 に並び替えたい場合は、次の1行で完了します。

list.sort(Comparator.comparing(Person::getAge));

何が起きているか

  • Person::getAge
    → 並び替えの基準(キー)を指定
  • Comparator.comparing(...)
    → そのキーを使って比較するComparatorを生成

つまり、

「年齢を取り出して、それを基準に並べ替える」

という意味になります。

4.3 文字列や日付をキーにする場合

キーが StringLocalDate の場合も、書き方は同じです。

例:名前順でソート

list.sort(Comparator.comparing(Person::getName));

例:誕生日順でソート

list.sort(Comparator.comparing(Person::getBirthDate));

このように、型を意識せずに同じ書き方ができるのが comparing の強みです。

4.4 プリミティブ専用の comparingInt など

age のような数値フィールドの場合、
次のような書き方もできます。

list.sort(Comparator.comparingInt(Person::getAge));

他にも、

  • comparingLong
  • comparingDouble

が用意されています。

なぜ使い分けるのか

  • comparing → ラッパークラス(Integer など)を使う
  • comparingInt → プリミティブ型を直接扱う

大きな差はありませんが、パフォーマンスや意図の明確さの点で、
数値の場合は comparingInt を使う方が好まれることがあります。

4.5 Comparatorを理解すると、次が一気に楽になる

ここまでで、

  • 昇順・降順
  • キー指定ソート

ができるようになりました。

この知識を土台にすれば、次に扱う

  • 複数条件のソート
  • 一部だけ降順にする
  • nullを安全に扱う

といった実務で必須のテクニックが、自然につながります。

5. 複数条件でソートする(実務で最も使われる形)

実際の業務データでは、
1つの条件だけで並び替えることは少ないのが現実です。

例えば、

  • 年齢が同じ場合は名前順にしたい
  • 優先度が同じ場合は更新日時で並べたい

といったケースです。

ここでは、Comparator を使った 複数条件ソートの基本と応用 を解説します。

5.1 thenComparing の基本

複数条件のソートは、
「第1条件 → 第2条件 → 第3条件…」
という形で考えます。

Javaでは thenComparing を使って、これを自然に表現できます。

例:年齢 → 名前の順でソート

list.sort(
    Comparator.comparingInt(Person::getAge)
              .thenComparing(Person::getName)
);

このコードの意味は、

  1. まず age で昇順ソート
  2. age が同じ場合は name で昇順ソート

という順番です。

5.2 一部の条件だけ降順にする

「基本は昇順だけど、一部の条件だけ降順にしたい」
というケースもよくあります。

例:スコアは降順、名前は昇順

list.sort(
    Comparator.comparingInt(Person::getScore).reversed()
              .thenComparing(Person::getName)
);

ここでのポイントは、

  • reversed()直前のComparatorにだけ適用される

という点です。

5.3 thenComparing にComparatorを渡す書き方

降順を明示したい場合は、
thenComparing にComparatorを渡す形もあります。

例:年齢昇順 → 登録日降順

list.sort(
    Comparator.comparingInt(Person::getAge)
              .thenComparing(
                  Comparator.comparing(Person::getRegisterDate).reversed()
              )
);

このように書くことで、
どの条件が昇順・降順なのかが明確になります。

5.4 実務でよくある複数条件の例

例:業務データの並び替え

list.sort(
    Comparator.comparingInt(Order::getPriority)
              .thenComparing(Order::getDeadline)
              .thenComparing(Order::getId)
);

この場合、

  1. 優先度が高い順
  2. 同じ優先度なら期限が近い順
  3. それでも同じならID順

という 安定した並び順 を作れます。

5.5 複数条件ソートは「読みやすさ」を意識する

複数条件ソートは、書けるようになると
一気に長く、読みにくくなりがちです。

そのため実務では、

  • 改行を入れる
  • 条件ごとにコメントを付ける

といった工夫をすると、保守性が上がります。

6. nullが含まれる場合のソート(実務で必ずぶつかる問題)

Comparator を使ったソートで、最もつまずきやすいのが null です。
特に業務データでは、

  • 一部の項目が未入力
  • 古いデータで値が入っていない

といった理由で、null が混ざることは珍しくありません。

このセクションでは、安全にソートするための基本パターンを解説します。

6.1 なぜ null があると例外が出るのか

次のようなコードを考えてみましょう。

list.sort(Comparator.comparing(Person::getName));

もし getName()null を返す要素があると、
比較時に NullPointerException が発生します。

これは、

  • Comparator比較できる値が返ってくる前提
  • null 同士、または null と非 null をどう比べるかが未定義

という理由によるものです。

6.2 nullsFirst / nullsLast を使う

Javaでは、null をどう扱うかを明示するための仕組みが用意されています。

nullを先頭にする場合

list.sort(
    Comparator.comparing(
        Person::getName,
        Comparator.nullsFirst(Comparator.naturalOrder())
    )
);

nullを末尾にする場合

list.sort(
    Comparator.comparing(
        Person::getName,
        Comparator.nullsLast(Comparator.naturalOrder())
    )
);

これで、

  • null を含んでも例外が出ない
  • 並び順が明確になる

という2つの問題を同時に解決できます。

6.3 オブジェクト自体が null の場合

まれに、Listの要素そのものが null というケースもあります。

List<Person> list = Arrays.asList(
    new Person("Alice", 20),
    null,
    new Person("Bob", 25)
);

この場合は、要素レベルでのnull対策が必要です。

list.sort(
    Comparator.nullsLast(
        Comparator.comparing(Person::getName)
    )
);

これにより、

  • null 要素は最後へ
  • null 要素同士は通常比較

という動作になります。

6.4 comparingIntnull の注意点

comparingInt などのプリミティブ用Comparatorは、
null を扱えません

Comparator.comparingInt(Person::getAge); // getAgeがintならOK

もし、Integer 型で null の可能性がある場合は、

Comparator.comparing(
    Person::getAge,
    Comparator.nullsLast(Integer::compare)
);

のように、明示的にComparatorを指定する必要があります。

6.5 null対策は「仕様」として明文化する

nullの扱いは、

  • 先頭に出すのか
  • 末尾に出すのか
  • そもそも弾くのか

という 仕様の問題 です。

コード上で nullsFirst / nullsLast を使うことで、
「意図」が明確になり、後から見た人にも優しい実装になります。

7. よくある落とし穴(バグを防ぐために知っておきたいポイント)

java list sort は一見シンプルですが、
知らないとバグや仕様違いにつながるポイントがいくつかあります。

ここでは、実務やレビューで指摘されやすい落とし穴を整理します。

7.1 ソートは「破壊的」であることを忘れない

list.sortCollections.sort は、
元のListを直接並び替える(破壊的操作) です。

List<Integer> original = new ArrayList<>(List.of(3, 1, 2));
List<Integer> reference = original;

original.sort(Comparator.naturalOrder());

この場合、

  • original
  • reference

両方がソート済み になります。

元の順序を残したい場合

List<Integer> sorted =
    new ArrayList<>(original);
sorted.sort(Comparator.naturalOrder());

または、

List<Integer> sorted =
    original.stream()
            .sorted()
            .toList();

「元のListを変更してよいか」 は、必ず意識しておくべきポイントです。

7.2 Comparatorの整合性(equalsとの関係)

Comparator は、次の性質を満たすことが望まれます。

  • 比較結果が一貫している
  • 推移律が成り立つ
  • equals と大きく矛盾しない

例えば、比較に使っている項目が不完全だと、
意図しない順序になることがあります。

Comparator.comparing(Person::getAge);

この場合、年齢が同じ人はすべて「同順位」となり、
並び順は保証されません。

対策

  • 安定した順序が必要な場合は
    thenComparing で第2条件を追加する
Comparator.comparingInt(Person::getAge)
          .thenComparing(Person::getId);

7.3 大文字・小文字の違い(文字列比較の罠)

String の自然順序は 大文字・小文字を区別します。

List<String> list = List.of("apple", "Banana", "orange");
list.sort(Comparator.naturalOrder());

結果は、直感と異なることがあります。

大文字・小文字を無視したい場合

list.sort(String.CASE_INSENSITIVE_ORDER);

業務要件として、

  • 表示用の並び替えか
  • 内部処理用の並び替えか

を意識して選ぶことが重要です。

7.4 ソート中に重い処理をしない

Comparatorの中で、

  • DBアクセス
  • 複雑な計算
  • I/O処理

を行うのは 避けるべき です。

// 悪い例(イメージ)
Comparator.comparing(p -> expensiveCalculation(p));

理由

  • ソート中は 比較が何度も呼ばれる
  • 想像以上にパフォーマンスが落ちる

対策

  • 事前にキーを計算して保持する
  • 単純なフィールド参照にする

7.5 「動く」より「意図が伝わる」コードを優先する

ソート処理は、

  • 書いた本人より
  • 後から読む人の方が多い

コードです。

そのため、

  • 無理に1行で書かない
  • Comparatorを変数に分ける
  • 条件ごとに改行する

といった工夫が、長期的な品質向上につながります。

8. 性能と選び方(どのソート方法を使うべきか)

ここまでで、List のソート方法は一通り理解できたはずです。
このセクションでは、性能や設計の観点から、どれを選ぶべきかを整理します。

8.1 list.sortstream().sorted() の使い分け

まず、最も迷いやすいのがこの2つです。

list.sort の特徴

  • 元のListを直接並び替える
  • コードが短く、意図が明確
  • 余計なオブジェクト生成がない
list.sort(Comparator.comparingInt(Person::getAge));

stream().sorted() の特徴

  • 元のListは変更されない
  • filter や map と組み合わせやすい
  • 新しいListを生成する
List<Person> sorted =
    list.stream()
        .sorted(Comparator.comparingInt(Person::getAge))
        .toList();

判断基準

  • 単純に並び替えたいlist.sort
  • データ加工の途中で並び替えたいstream().sorted()

8.2 大量データを扱う場合の注意点

ソート処理は、データ量が増えるほど負荷が高くなります。

比較回数が多いことを意識する

ソート中は、Comparatorが 何度も呼び出されます
そのため、Comparator内では次の点に注意しましょう。

  • 重い処理を入れない
  • メソッドチェーンを深くしすぎない
  • 可能ならプリミティブ用Comparatorを使う

例:キーを事前に計算する

class Person {
    private int age;
    private int ageRank; // 事前計算したキー
}

このようにしておくと、比較処理が軽くなります。

8.3 Collections.sort を使う場面はある?

新規コードでは、ほぼ使う必要はありません

ただし、

  • Java 7以前のコードを読む
  • 古いライブラリとの整合性を保つ

といった場面では、
Collections.sort の存在を知っておくことは重要です。

8.4 実務でのおすすめ指針(まとめ)

迷ったときは、次の指針を思い出してください。

  • 基本は list.sort
  • 非破壊が必要なら stream().sorted()
  • Comparator.comparing を軸に考える
  • nullの扱いは必ず明示する

これらを守るだけで、
バグが少なく、読みやすいソート処理が書けるようになります。

9. まとめ(java list sort チートシート)

最後に、この記事で解説してきた内容を
「目的別にすぐ思い出せる形」 で整理します。

実務では毎回細かい書き方を暗記する必要はありません。
考え方と基本パターン を押さえておけば十分です。

9.1 最短でListをソートしたいとき

昇順(自然順序)

list.sort(Comparator.naturalOrder());

降順

list.sort(Comparator.reverseOrder());

👉 数値・文字列・日付など、基本型はこれで対応可能。

9.2 オブジェクトのListをキーでソートする

単一条件(例:年齢順)

list.sort(Comparator.comparingInt(Person::getAge));

文字列や日付の場合

list.sort(Comparator.comparing(Person::getName));

👉 「どの項目で並べたいか」= comparing の引数

9.3 複数条件でソートする

基本形(例:年齢 → 名前)

list.sort(
    Comparator.comparingInt(Person::getAge)
              .thenComparing(Person::getName)
);

一部だけ降順にする

list.sort(
    Comparator.comparingInt(Person::getScore).reversed()
              .thenComparing(Person::getName)
);

👉 実務では thenComparing が最重要

9.4 nullを安全に扱う

フィールドがnullになる可能性がある場合

list.sort(
    Comparator.comparing(
        Person::getName,
        Comparator.nullsLast(Comparator.naturalOrder())
    )
);

要素そのものがnullの場合

list.sort(
    Comparator.nullsLast(
        Comparator.comparing(Person::getName)
    )
);

👉 nullの扱いは必ず明示する

9.5 元のListを変更したくない場合

List<Person> sorted =
    list.stream()
        .sorted(Comparator.comparingInt(Person::getAge))
        .toList();

👉 非破壊が必要なら stream().sorted()

9.6 迷ったときの判断基準(重要)

  • 基本は list.sort
  • 並び替え条件は Comparator.comparing
  • 複数条件は thenComparing
  • nullは nullsFirst / nullsLast
  • 元データを守りたいなら Stream

9.7 おわりに

java list sort は、
Javaを使う上で 避けて通れない基本スキル です。

最初は複雑に見えても、

  • 「Comparatorが順序を決める」
  • 「sortは実行するだけ」

という役割分担を理解すれば、
どんな並び替えにも対応できるようになります。

この記事が、
「毎回調べ直していたListソート」から卒業するきっかけになれば幸いです。