Java ComparableとComparatorの違いについて
今回はJavaのオブジェクト同士を比較するための以下の二つのインタフェースの違いについて、勉強したことをまとめる。
java.lang.Comparable<T>
インタフェースjava.util.Comparator<T>
インタフェース
どちらもオブジェクトを順序付けし、比較するためのインタフェースだが、
今回は例として、以下のid
とname
フィールドだけを持つ従業員クラスEmployee
を比較することを考える。
class Employee implements Comparable<Employee> { private int id; private String name; public Employee(int id, String name) { this.id = id; this.name = name; } public int getId() { return this.id; } public String getName() { return this.name; } public String toString() { return this.id + ":" + this.name; } @Override public int compareTo(Employee emp) { return this.id - emp.id; } }
このときに、TreeSet
を使って、以下を実行する。
TreeSet
は順序性を持つ集合であり、内部的にオブジェクト同士の比較を行っている。
public class Work { public static void main(String[] args) { Set<Employee> empSet = new TreeSet<>(); empSet.add(new Employee(2, "Yamada")); empSet.add(new Employee(1, "Sato")); empSet.add(new Employee(3, "Suzuki")); System.out.println(empSet); } }
実行結果は以下が出力される。
[1:Sato, 2:Yamada, 3:Suzuki]
id
順になっていることが分かる。
java.util.Comparator<T>インタフェース
java.util.Comparator<T>
インタフェースの特徴は以下。
- 二つのオブジェクトを比較するためのインタフェース
- 比較処理を実装した新規クラスとしてインタフェースを実装する(ただし後述の
Comparator.comparing
メソッドで生成可能) - 比較したいオブジェクト二つを引数に取る
compare
メソッドをオーバーライドする compare(T o1, T o2)
は二つのオブジェクトの大小関係に応じて以下の値を返す- o1がo2より大きい => 正の値
- o1がo2より小さい => 負の値
- o1とo2は同じ大きさ => 0
・原則としてSerializable
インタフェースも実装する※以下の実装では省略
今度は、従業員の名前の辞書順で従業員同士を比較するためのComparator
を実装する。
なお、Employee
クラスは先ほど実装したようにComparable
インタフェースを実装したクラスのままとする。
class EmployeeNameComparator implements Comparator<Employee> { @Override public int compare(Employee emp1, Employee emp2) { return emp1.getName().compareTo(emp2.getName()); } }
このときに、以下を実行するとname
で辞書順にソートされた結果が得られる。
Employee
クラスは上でComparable
を実装したため標準ではid
でソートするが、
今回はTreeMap
のコンストラクタにEmployeeNameComparator
を明示することでソートをname
の辞書順に変更している。
public class Work { public static void main(String[] args) { Set<Employee> empSet = new TreeSet<>(new EmployeeNameComparator()); empSet.add(new Employee(2, "Yamada")); empSet.add(new Employee(1, "Sato")); empSet.add(new Employee(3, "Suzuki")); System.out.println(empSet); } }
実行結果は以下が出力される。
[1:Sato, 3:Suzuki, 2:Yamada]
name
の辞書順にソートされていることが分かる。
Comparator.comparingメソッド
今回、Compartor
クラスを実装したクラスを新規に作成したが、
その場限りで順序を指定した場合などに毎回新規クラスを実装するのは煩わしい。
そこで、Java SE 8からはComparator
インタフェースのstaticメソッドとしてcomparing
メソッドが追加された。
これはcompare
メソッドを適切にオーバーライドしたComparator
クラスを生成するもので、以下のようなシグネチャを持つ。
static <T,U extends Comparable<? super U>>Comparator<T> comparing(Function<? super T,? extends U> keyExtractor)
このままだと非常にわかりづらいが、要するに、
ソートキーとなる値を返すFunction
を引数に与えると、適切にそのキーで比較してくれるComparator
を返してくれるというものである。
これはラムダ式を使って簡単に書けるので、TreeMap
コンストラクタの引数に直接書くことでシンプルに比較順を変更できる。
具体的に実装例を見た方が分かりやすいので、先ほど作ったEmployeeNameComparator
と同等の実装をしてみる。
ラムダ式には、比較したいキーとなる値を返す関数を指定すればよいので、getName
をメソッド参照すると簡潔に書ける。
public class Work { public static void main(String[] args) { Set<Employee> empSet = new TreeSet<>(Comparator.comparing(Employee::getName)); empSet.add(new Employee(2, "Yamada")); empSet.add(new Employee(1, "Sato")); empSet.add(new Employee(3, "Suzuki")); System.out.println(empSet); } }
実行結果は以下が出力される。
[1:Sato, 3:Suzuki, 2:Yamada]
name
の辞書順にソートされていることが分かる。
同じ結果が得られるので、こちらの方がより簡潔で見やすい。
何より、キーを指定するのみで、生々しい比較ロジックを書く必要がないのが便利。
まとめ
Javaのオブジェクト比較のための二つのインタフェースの違いについて見た。
Comparable
はそのオブジェクト自身が本来持つデフォルトの比較ルールを実装するのに対し、
Comparator
は外から特定クラスのオブジェクト同士の比較ルールの適用を可能とする。
さらに、Comparator
はComparator.comparing
メソッドを使えば、ラムダ式を使って簡潔に書けるので便利。
参考文献
Effective Java 第2版 項目12「Comparableの実装を検討する」
EFFECTIVE JAVA 第2版 (The Java Series)
- 作者: Joshua Bloch,柴田芳樹
- 出版社/メーカー: 丸善出版
- 発売日: 2014/03/11
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (10件) を見る
以上