JavaのComparatorを使ってソートする

Comparatorを使ってソートする方法について、触ってみました。

Comparator (Java Platform SE 8)

Comparatorはその名の通り、あるオブジェクト同士を比較する役割を持っています。Comparatorは導入されたのが1.2ですが、FunctionalInterfaceアノテーションが付与されているのでラムダ式を使って定義できるみたいですね。例えば、こんな感じかなと。

var comparator = (value1, value2) -> value1 - value2;

Comparatorが返す値はこう言う意味になります。よくごちゃごちゃになります。いい覚え方ないんでしょうかね。

  • 最初の引数が2番目の引数より小さい場合は負の整数。
  • 等しい場合は0。
  • 最初の引数が2番目の引数より大きい場合は正の整数。

https://docs.oracle.com/javase/jp/8/docs/api/java/util/Comparator.html#compare-T-T-

Stream API でソートしてみる

Stream APIで値をソートするのは簡単です。

https://docs.oracle.com/javase/jp/8/docs/api/java/util/stream/Stream.html#sorted-java.util.Comparator-

Stream API には sorted(Comparator) と言うメソッドが定義されているので、こいつを使ってあげればOKです。

import java.util.stream.Stream;

public class ComparatorExample1 {

    public static void main(String[] args) {
        Stream.of(10, 15, 9, 7, 12)
                .sorted((value1, value2) -> value1 - value2)
                .forEach(System.out::println);
    }

}

このようにソートされます。いたって普通ですね。まぁ、intの値をソートするユースケースは現実世界でそんなに存在しないかな。

7
9
10
12
15

もう少し複雑な例を。

import java.util.Comparator;
import java.util.stream.Stream;

public class ComparatorExample2 {

    public static void main(String[] args) {
        Stream.of(new Order("T-N001", 10),
                        new Order("T-M001", 300),
                        new Order("X-S002", 50))
                .sorted(Comparator.comparing(Order::amount))
                .forEach(System.out::println);
    }

    public record Order(String orderId, int amount) {

    }

}

いっちょ前にrecodクラスを使ってみました。

Comparator.comparing を使えば任意の型からint値を使ってソートすることができます。便利じゃないですか?ひとつ前の例(ComparatorExample1)で書いていたようなラムダ式を書かなくていいわけで、より洗礼されていて読みやすくなっていますね。意図が明確というか。

出力はこうなります。

Order[orderId=T-N001, amount=10]
Order[orderId=X-S002, amount=50]
Order[orderId=T-M001, amount=300]

きちんと amount の値でソートされましたね。

複数項目でソートしてみる

ある型を複数項目でソートしたい場合ってあると思います。そんなときもComparatorを使うとサクッとソートできるようです。

こんな感じで。

import java.util.stream.Stream;

import static java.util.Comparator.comparing;

public class ComparatorExample3 {

    public static void main(String[] args) {
        Stream.of(new Order("T-N001", 10),
                        new Order("B-M001", 300),
                        new Order("A-M001", 300),
                        new Order("X-S002", 50))
                .sorted(comparing(Order::amount).thenComparing(Order::orderId))
                .forEach(System.out::println);
    }

    public record Order(String orderId, int amount) {

    }

}

comparingメソッドはstaticインポートに変更しました。そして、thenComparingを使って2個目のソート処理を指定しました。このメソッドはかなり洗礼されているなーと感じていて、amountでソートし、orderIdでソートすることが一発で分かるようになっています。これをfor文で実装するとなるとどれだけ可読性が落ちるのか...。

出力はこうなります。きちんとソートされてますね。

Order[orderId=T-N001, amount=10]
Order[orderId=X-S002, amount=50]
Order[orderId=A-M001, amount=300]
Order[orderId=B-M001, amount=300]