Comparatorの使い方・作り方

Java SE 8のdefaultメソッドの導入や関数合成などを取り入れ大幅に刷新された。

概要

Comparatorクラス図

Comparatorクラス図

コレクションフレームワークの部品。Comparatorを実装することでComparableではないオブジェクトもソートできるようになる。

Comparatorでabstractなのはcompare()メソッドしかないのでこれを実装する。オブジェクトの大小比較し結果をint型の戻り値によって返す。

  • o1 < o2 なら -1

  • o1 == o2 なら 0

  • o1 > o2 なら -1

Java SE 8より前であればcompare()を頑張って実装するしかなかったが、defaultメソッドの導入や関数合成などを取り入れ大幅に刷新された。関数合成で組み合わせることで、compare()を書くことがほとんどいらない。

使い方

Java SE 8より前はCollectionsによるソートが主だったが、利用できるクラスやメソッドも増えた。

Table 1. Comparatorを利用できる主なクラスとメソッド
クラス・メソッド 概要

Steram#sorted()

Java SE 8から。ソートの結果を別のコレクションで返すことになる。
min()やmax()もある。

List#sorted()

Java SE 8から。なぜかなかった。

Collections.sort()

Java SE 8より前ならこれ。

Arrays.sort()

配列をソートする。

Streamで並べ替えると元のコレクションとは別のコレクションを生成することになるので有利。あちこちでソート結果を共有してしまうと別のソートをしたくなったときに困るので、ソート結果はソート前のコレクションとは別のコレクションにしておくのがベター。

Comparatorを利用したソート例
List<Integer> numbers = Arrays.asList(3, 2, 4, 1);

//Streamによるソート
List<Integer> sorted = numbers.stream()
                              .sorted(Comparator.naturalOrder())
                              .collect(Collectors.toList());

//Collectionsによるソート
Collections.sort(numbers,Comparator.naturalOrder());

//List#sort()によるソート
numbers.sort(Comparator.naturalOrder());

//Arraysによるソート
Integer[] numberArray = new Integer[] { 3, 2, 4, 1 };
Arrays.sort(numberArray, Comparator.naturalOrder());

作り方

compare()を実装する以外にも、defaultメソッドや関数合成などが利用できる。compare()を直接書くことがいらなくなってきた。

ComparableをソートするComparatorを作る

Comparableなものは多いので使うことが多い。ただ自然順序順であればComparatorを指定しなくてよい場合もある。

Table 2. メソッドの説明
メソッド 概要

naturalOrder()

自然な順序。

reverseOrder()

自然順序付けの逆。

Comparableをソートする例
List<Integer> numbers = Arrays.asList(3, 2, 4, 1);

List<Integer> naturalSorted = numbers.stream()
                                     .sorted(Comparator.naturalOrder())
                                     .collect(Collectors.toList());
List<Integer> reverseSorted = numbers.stream()
                                     .sorted(Comparator.reverseOrder())
                                     .collect(Collectors.toList());

System.out.println("numbers:" + numbers);
System.out.println("naturalSorted:" + naturalSorted);
System.out.println("reverseSorted:" + reverseSorted);
出力
numbers:[3, 2, 4, 1]
naturalSorted:[1, 2, 3, 4]
reverseSorted:[4, 3, 2, 1]

ComparableでないオブジェクトのComparatorを作る

これまでであればcompareTo()がマストだったが、オブジェクト内のComparableを利用して簡単に作れるメソッドが追加された。

comparing()はこの他にプリミティブ型に対応している「comparingInt()」「comparingLong()」「comparingDouble()」などもある。

Table 3. メソッドの説明
メソッド 概要

comparing(Function<? super T,? extends U> keyExtractor)

keyExtractorで指定された値で比較するComparator。

オブジェクトをソートする例
List<Person> persons = new ArrayList<>();
persons.add(new Person("taro", 25));
persons.add(new Person("jiro", 22));
persons.add(new Person("saburo", 23));

Comparator<Person> nameComparator = Comparator.comparing(Person::getName);
List<Person> sortedPersons = persons.stream()
                                   .sorted(nameComparator)
                                   .collect(Collectors.toList());

System.out.println("   original:" + persons);
System.out.println("name sorted:" + sortedPersons);
出力
   original:[Person(name=taro, age=25), Person(name=jiro, age=22), Person(name=saburo, age=23)]
name sorted:[Person(name=jiro, age=22), Person(name=saburo, age=23), Person(name=taro, age=25)]

Comparatorを組み合わせてComparatorを作る

SQLであれば「ORDER BY colA,colB」みたいなことを、いままではcompareTo()を頑張るしかなかったが、これも簡単にできるメソッドが追加された。

いわゆる関数合成。いくらでも組み合わせることができる。この方法を用いればホントに特殊なモノ以外はcompareTo()を直接書かずにComparatorを作れる。

Table 4. メソッドの説明
メソッド 概要

reversed()

自然な順序。

thenComparing(Comparator<? super T> other)

compareTo()==0の場合にotherが適用されるComparator

関数合成を利用したソートの例
List<Person> persons = new ArrayList<>();
persons.add(new Person("taro", 25));
persons.add(new Person("jiro", 22));
persons.add(new Person("saburo", 23));
persons.add(new Person("shiro", 25));

Comparator<Person> personComparator =
       Comparator.comparing(Person::getAge)
                  .reversed()
                  .thenComparing(Comparator.comparing(Person::getName));

List<Person> sortedPersons = persons.stream()
                                    .sorted(personComparator)
                                    .collect(Collectors.toList());

System.out.println("original:" + persons);
System.out.println("  sorted:" + sortedPersons);
出力
original:[Person(name=taro, age=25), Person(name=jiro, age=22), Person(name=saburo, age=23), Person(name=shiro, age=25)]
  sorted:[Person(name=shiro, age=25), Person(name=taro, age=25), Person(name=saburo, age=23), Person(name=jiro, age=22)]