Java Tips | コレクション:逆順ソート

Java Java
スポンサーリンク

逆順ソートは「優先度の高いものを先頭に持ってくる」ための技

ソートというと「小さい順・古い順」をイメージしがちですが、
業務ではむしろ「新しい順」「優先度の高い順」「金額の大きい順」が圧倒的に多いです。

つまり、逆順ソートは“ビジネス的に重要なものを先頭に持ってくるための技”です。
ここをきちんとユーティリティ化しておくと、
「毎回 Comparator をひねり出す」苦労から解放されます。


基本:数値や文字列を逆順ソートする

Collections.reverseOrder / Comparator.reverseOrder を使う

まずは一番シンプルな「自然順の逆」を押さえましょう。
数値や文字列など、「普通に並べる順番」が決まっている型は、
reverseOrder を使うだけで逆順にできます。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class ReverseSortBasic {

    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(10);
        numbers.add(5);
        numbers.add(20);

        // 昇順
        Collections.sort(numbers);
        System.out.println(numbers); // [5, 10, 20]

        // 逆順(大きい順)
        numbers.sort(Comparator.reverseOrder());
        System.out.println(numbers); // [20, 10, 5]
    }
}
Java

ここで重要なのは、
Comparator.reverseOrder() は“自然順の完全な逆”を作ってくれる」
という感覚です。

文字列も同じです。

List<String> names = new ArrayList<>(List.of("山田", "佐藤", "鈴木"));

names.sort(Comparator.reverseOrder());
System.out.println(names); // 自然順の逆(辞書順の逆)
Java

「とりあえず逆順にしたい」なら、
Comparator.reverseOrder() を覚えておけば十分戦えます。


オブジェクトを「特定の項目の逆順」でソートする

comparingInt(…).reversed() という“定番パターン”

業務では、「オブジェクトの特定のフィールドを基準に逆順ソート」がほぼ定番です。
例えば、「年齢の高い順に並べたい」ユーザークラスを考えます。

class User {
    private final String name;
    private final int age;
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() { return name; }
    public int getAge() { return age; }
}
Java

これを「年齢の高い順」に並べるコードはこう書けます。

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class ReverseSortUser {

    public static void main(String[] args) {
        List<User> users = new ArrayList<>(
                List.of(
                        new User("山田", 30),
                        new User("佐藤", 25),
                        new User("鈴木", 40)
                )
        );

        users.sort(
                Comparator.comparingInt(User::getAge)
                          .reversed()
        );

        for (User u : users) {
            System.out.println(u.getName() + " " + u.getAge());
        }
        // 鈴木 40
        // 山田 30
        // 佐藤 25
    }
}
Java

ここで深掘りしたいポイントは二つです。

一つ目は、Comparator.comparingInt(User::getAge)
「年齢の昇順」というルールを表し、
.reversed() がそれを「年齢の降順」にひっくり返している、という構造です。

二つ目は、「“何で並べるか”を先に書いてから、最後に .reversed() を付ける」
という書き方にすると、“仕様としての読みやすさ”が格段に上がることです。


複数条件の逆順ソートをきれいに書く

「まず日付の新しい順、同じ日付ならIDの昇順」

業務では、「まずこれの逆順、同じならこれの昇順」という複合条件がよく出ます。
例えば、注文を「注文日の新しい順、同じ日付なら注文IDの昇順」で並べたいとします。

import java.time.LocalDate;

class Order {
    private final String id;
    private final LocalDate orderedAt;
    public Order(String id, LocalDate orderedAt) {
        this.id = id;
        this.orderedAt = orderedAt;
    }
    public String getId() { return id; }
    public LocalDate getOrderedAt() { return orderedAt; }
}
Java

Comparator で書くとこうなります。

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class ReverseSortOrder {

    public static void main(String[] args) {
        List<Order> orders = new ArrayList<>(
                List.of(
                        new Order("A003", LocalDate.of(2024, 1, 10)),
                        new Order("A001", LocalDate.of(2024, 1, 12)),
                        new Order("A002", LocalDate.of(2024, 1, 12))
                )
        );

        orders.sort(
                Comparator.comparing(Order::getOrderedAt)
                          .reversed()                 // 日付の新しい順
                          .thenComparing(Order::getId) // 同じ日付ならID昇順
        );
    }
}
Java

ここでの重要ポイントは、
「昇順の Comparator を組み立ててから、必要なところだけ .reversed() を付ける」
というスタイルです。

これにより、
「まず注文日で並べる(逆順)→同じならIDで並べる(昇順)」
という仕様が、そのままコードとして読めるようになります。


逆順ソートと「コピーしてからソート」のユーティリティ化

「元データはそのまま、逆順ソート済みのリストが欲しい」

実務では、「元のリストは触らず、逆順に並んだ結果だけ欲しい」ことが多いです。
そのたびにコピー+ソートを書くのは面倒なので、ユーティリティにしてしまいましょう。

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public final class ReverseSortedLists {

    private ReverseSortedLists() {}

    public static <T> List<T> reversed(List<T> source, Comparator<? super T> comparator) {
        if (source == null || source.isEmpty()) {
            return List.of();
        }
        List<T> copy = new ArrayList<>(source);
        copy.sort(comparator.reversed());
        return List.copyOf(copy); // 逆順ソート済み不変List
    }
}
Java

使い方のイメージです。

List<Integer> numbers = List.of(10, 5, 20);

List<Integer> desc =
        ReverseSortedLists.reversed(numbers, Comparator.naturalOrder());

System.out.println(desc); // [20, 10, 5]
// desc.add(1); // UnsupportedOperationException
Java

ここでの重要ポイントは、
「逆順ソートと“不変化”をセットで扱っている」ことです。

「この結果は“この順番で固定”」という意図を、
戻り値の性質で保証できます。


null を含むデータの逆順ソート

null を最後に寄せる逆順ソート

現実のデータには null が混ざるので、
逆順ソートでも「null をどこに置くか」を決めておく必要があります。

import java.util.Comparator;

public final class NullSafeReverseSorts {

    private NullSafeReverseSorts() {}

    public static final Comparator<Integer> INT_DESC_NULLS_LAST =
            Comparator.nullsLast(Comparator.<Integer>naturalOrder().reversed());
}
Java

使い方のイメージです。

List<Integer> nums = new java.util.ArrayList<>(
        List.of(10, null, 5, 20)
);

nums.sort(NullSafeReverseSorts.INT_DESC_NULLS_LAST);
// [20, 10, 5, null] のような並び
Java

ここでのポイントは、
nullsLast.reversed() の組み合わせ順を意識する」ことです。

Comparator.nullsLast(昇順).reversed()
Comparator.nullsLast(昇順.reversed()) は挙動が変わるので、
「null を最後にしたいのか、先頭にしたいのか」を意識して書き分ける必要があります。


まとめ:逆順ソートユーティリティで意識してほしいこと

逆順ソートは、
「ビジネス的に“重要なものを先頭に持ってくる”ための標準テクニック」です。

自然順の逆なら Comparator.reverseOrder() を素直に使う。
オブジェクトなら Comparator.comparing(...).reversed() で「何を基準に逆順か」を明示する。
複数条件では「どこを逆順にするか」を Comparator チェーンで表現する。
元データを壊したくないときは「コピーして逆順ソート+不変化」をユーティリティにしておく。
null を含む場合は nullsFirst / nullsLast.reversed() の組み合わせを意識して、「null の位置」も仕様として決める。

あなたの逆順ソートのコードが、
「なんとなく大きい順」ではなく
「ビジネス的な優先度順」として読めるようになってきたら、
それはもう立派に“業務で使える逆順ソートユーティリティ”を扱えている状態です。

タイトルとURLをコピーしました