状態・期限・nullの順に並べ替え条件を重ねる図。ソート順を仕様から決める

実装・エラー解決

JavaでListを複数条件でソートする方法|thenComparingとnullsLastの使い方

作業一覧を「未着手を先、同じ状態なら期限が近い順」に並べた。最初はよく見えたのに、期限が未設定のデータが来ると例外になり、同じ期限の行も表示のたびに順序が揺れる。

複数条件のソートは、比較式を一行へ詰めるより、利用者が期待する順序を表にしてから Comparator を組む方が安全です。以下は Java 21 の例です。

まず並び順を文章ではなく表にする

優先順位比較する値順序
1状態未着手 → 対応中 → 完了
2期限早い日付 → 遅い日付 → 未設定
3ID小さい順。完全同順位の決着用

「期限なし」は最後にします。ここを決めずに Comparator.comparing(Ticket::dueDate) と書くと、null の比較時に NullPointerException になり得ます。また、画面に同じ状態・期限のチケットが複数あるなら、IDまで比べて順序を固定します。

thenComparing で上から順に比較する

複数条件ソートの優先順。状態:業務上の優先度。期限:同じ状態の中で昇順。null:最後に寄せる。同順位:順序の期待値を確認
複数条件ソートの優先順。thenComparingとnullの扱い
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

enum State { OPEN, IN_PROGRESS, DONE }
record Ticket(long id, State state, LocalDate dueDate) {}

class TicketOrder {
    static int priority(State state) {
        return switch (state) {
            case OPEN -> 0;
            case IN_PROGRESS -> 1;
            case DONE -> 2;
        };
    }

    static final Comparator<Ticket> ORDER =
            Comparator.comparingInt((Ticket t) -> priority(t.state()))
                    .thenComparing(Ticket::dueDate,
                            Comparator.nullsLast(Comparator.naturalOrder()))
                    .thenComparingLong(Ticket::id);

    public static void main(String[] args) {
        List<Ticket> tickets = new ArrayList<>(List.of(
                new Ticket(4, State.OPEN, null),
                new Ticket(2, State.IN_PROGRESS, LocalDate.of(2026, 9, 24)),
                new Ticket(3, State.OPEN, LocalDate.of(2026, 9, 23)),
                new Ticket(1, State.OPEN, LocalDate.of(2026, 9, 23))));
        tickets.sort(ORDER);
        System.out.println(tickets.stream().map(Ticket::id).toList());
        // [1, 3, 4, 2]
    }
}

thenComparing は前の比較が同じ場合だけ次の条件を使います。OPEN同士なら期限を見て、それも同じならIDを見る、という動きです。enumの宣言順をそのまま優先順位に使う手もありますが、表示順を業務仕様として明示するため、この例では switch にしました。

降順にする場所を間違えない

「期限が新しいものから。ただし未設定は最後」に変えるなら、期限の比較器だけを逆順にします。

.thenComparing(Ticket::dueDate,
        Comparator.nullsLast(Comparator.reverseOrder()))

比較器全体の最後へ .reversed() を付けると、状態とIDの順序まで逆になり、nullsLast の意図も崩れます。逆にしたい条件の内側へ逆順を置いてください。

List自体を並べ替えるか、新しいListを作るか

上の tickets.sort(ORDER) は元のListを書き換えます。List.of(...) から作ったListには変更操作を使えないため、例では new ArrayList<>(...) にしています。元データを残したいなら new ArrayList<>(original) を作ってからソートします。ただし要素オブジェクトまで複製されるわけではありません。

確認時は、状態が違う、状態だけ同じ、期限まで同じ、期限が null、全件同順位を含む入力を用意します。特にデータベースからページごとに取得する一覧では、Java側で取得後に並べ替えるだけではページ全体の順序を保証できません。DB問い合わせ側の ORDER BY に同じ条件を反映する必要があります。

参考資料

-実装・エラー解決
-,