Java ComparableとComparator どちらを使うか

本記事のコードは**JDK 1.8**で書いています。
# はじめに
自分で作ったクラスのオブジェクト同士の比較をする場合、何をもって大小とするかの比較ロジックを定義する必要があります。
定義方法には以下の２つがあります。

- オブジェクトのクラスに`Comparable`インタフェースを実装させ、`compareTo`メソッドで定義。
- `Comparator`インタフェースを実装したクラスを定義し、`compare`メソッドで定義。

どちらの方法でも定義可能なのですが、どちらで定義するかの指針みたいなのはあるので、それを説明します。
# 比較対象のクラス
本の情報を持つ`Book`クラスを比較することを考えます。

```java
public class Book {

    // タイトル
    private String title;

    // 値段
    private int price;

    // 発行日
    private LocalDate dateOfIssue;

}
```



# `Comparable`による定義
`java.lang`の`Comparable`インタフェースを実装し、`compareTo`メソッドをオーバーライドして比較ロジックを定義します。
例えば`title`で比較させたい場合は、以下のように実装します。

```java

public class Book implements Comparable<Book>{

    // タイトル
    private String title;

    // 値段
    private int price;

    // 発行日
    private LocalDate dateOfIssue;

    @Override
    public int compareTo(Book book){
        return title.compareTo(book.title);
    }

}
```

`title`の比較結果をそのまま返すようにしています。`Book`オブジェクトのリストをソートすると、`title`の昇順で並び替えられます。

**ソート例**

```java
	Book book1 = new Book();
	book1.setTitle("ドラえもん");
	book1.setPrice(500);
	book1.setDateOfIssue(LocalDate.of(2010,10,10));

	Book book2 = new Book();
	book2.setTitle("パーマン");
	book2.setPrice(400);
	book2.setDateOfIssue(LocalDate.of(2012,10,10));

	Book book3 = new Book();
	book3.setTitle("オバQ");
	book3.setPrice(300);
	book3.setDateOfIssue(LocalDate.of(2014,10,10));

	List<Book> bookList = Arrays.asList(book1, book2, book3);

	System.out.println("ソート前：" + bookList);

	Collections.sort(bookList);

	System.out.println("ソート後：" + bookList);
```

`java.util.Collections`の`sort(List<T> list)`は、要素のオブジェクトが持つ`compareTo`により要素同士を比較し、ソート実施します。

**実行結果**

```
ソート前：[Book(title=ドラえもん, price=500, dateOfIssue=2010-10-10), Book(title=パーマン, price=400, dateOfIssue=2012-10-10), Book(title=オバQ, price=300, dateOfIssue=2014-10-10)]
ソート後：[Book(title=オバQ, price=300, dateOfIssue=2014-10-10), Book(title=ドラえもん, price=500, dateOfIssue=2010-10-10), Book(title=パーマン, price=400, dateOfIssue=2012-10-10)]
```


`compareTo`では**比較ロジックを一つしか定義できない**ことに注意してください。

# `Comparator`による定義
`java.util`の`Comparator`を実装したクラスを作成し、`compare`メソッドをオーバーライドして比較ロジックを定義します。例えば`title`で比較させたい場合は、以下のように実装します。

```java
public class BookTitleComparator implements Comparator<Book> {

    @Override
    public int compare(Book book1, Book book2){
        return book1.getTitle().compareTo(book2.getTitle());
    }

}
```
これを使ってソートさせてみましょう。

**ソート例**

```java
		Book book1 = new Book();
		book1.setTitle("ドラえもん");
		book1.setPrice(500);
		book1.setDateOfIssue(LocalDate.of(2010,10,10));

		Book book2 = new Book();
		book2.setTitle("パーマン");
		book2.setPrice(400);
		book2.setDateOfIssue(LocalDate.of(2012,10,10));

		Book book3 = new Book();
		book3.setTitle("オバQ");
		book3.setPrice(300);
		book3.setDateOfIssue(LocalDate.of(2014,10,10));

		List<Book> bookList = Arrays.asList(book1, book2, book3);

		System.out.println("ソート前：" + bookList);

		bookList.sort(new BookTitleComparator());

		System.out.println("ソート後：" + bookList);
```

**実行結果**

```
ソート前：[Book(title=ドラえもん, price=500, dateOfIssue=2010-10-10), Book(title=パーマン, price=400, dateOfIssue=2012-10-10), Book(title=オバQ, price=300, dateOfIssue=2014-10-10)]
ソート後：[Book(title=オバQ, price=300, dateOfIssue=2014-10-10), Book(title=ドラえもん, price=500, dateOfIssue=2010-10-10), Book(title=パーマン, price=400, dateOfIssue=2012-10-10)]
```

`Comparator`を実装したクラスは、以下のように**比較ロジックに応じていくつも作ることができます**。

**値段比較の`Comparator`**

```java
public class BookPriceComparator implements Comparator<Book> {

    @Override
    public int compare(Book book1, Book book2){
        return Integer.valueOf(book1.getPrice()).compareTo(book2.getPrice());
    }

}
```

**発行日比較の`Comparator`**

```java
public class BookDateOfIssueComparator implements Comparator<Book> {

    @Override
    public int compare(Book book1, Book book2){
        return book1.getDateOfIssue().compareTo(book2.getDateOfIssue());
    }

}
```

## 追記

`Comparator`のstaticメソッド`comparing`を使えば、クラスを作る必要もありません。

```java

// タイトル順
bookList.sort(Comparator.comparing(Book::getTitle));

// 値段順
bookList.sort(Comparator.comparing(Book::getPrice));

// 発行日順
bookList.sort(Comparator.comparing(Book::getDateOfIssue));
```

# で、どっち使う？
結論から言いますと、**基本的に`Comparator`を使うのがよい**かと思います。

`Comparable`はクラスに属する`compareTo`を一つしか定義できないので、そのクラスにとって最も自然と思われる比較ロジックを実装すべきと言われています。例えば`LocalDate`は日付同士の比較が自然と思われるため、「前の日付 < 後の日付」のロジックで`compareTo`が実装されています。

では今回の`Book`ではどのフィールドで比較するのが自然なのでしょうか？なんとなくタイトルかな？と思われますが、人によっては発行日での比較が自然な可能性もありますし、将来`bookId`なんてフィールドが追加されたらさらにブレますよね？もうバグの予感しかしませんね。

`Comparator`は**クラス名ではっきり何の比較かを明示できます**ので、このようなブレを防ぐことができます。また、タイトルが同じ場合は発行日でさらに比較…のような複合的な比較をしたい場合も、別途`Comparator`実装クラスを追加すれば可能です。

# まとめ
- `Comparable`での比較ロジックは自然な比較にするべきだが、何が自然かは状況によって変わる。
- `Comparator`は実装するクラス名で比較ロジックの内容を明示できる。
- `Comparable`では１つの比較ロジックしか実装できないが、`Comparator`ではクラスの数だけ実装可能。
- 自作のクラス同士の比較ロジックは基本的に`Comparator`で実装しよう！

# 参考
[Java本格入門 ～モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで](https://www.amazon.co.jp/Java%E6%9C%AC%E6%A0%BC%E5%85%A5%E9%96%80-%EF%BD%9E%E3%83%A2%E3%83%80%E3%83%B3%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB%E3%81%AB%E3%82%88%E3%82%8B%E5%9F%BA%E7%A4%8E%E3%81%8B%E3%82%89%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E3%83%BB%E5%AE%9F%E7%94%A8%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%BE%E3%81%A7-%E8%B0%B7%E6%9C%AC-%E5%BF%83-ebook/dp/B071D8RYR5)
[Comparable (Java Platform SE 8 )](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/Comparable.html)
[Comparator (Java Platform SE 8 )](https://docs.oracle.com/javase/jp/8/docs/api/java/util/Comparator.html)