[Java] Comparable, Comparator のメモ


# 目的

- 大小比較をする `java.util.Comparator` と `java.lang.Comparable` に関するクラス・メソッドの整理
- `Comparator` や `Comparable` を実装する場合の注意点を整理

# 基本的な話

## `Comparable` , `Comparator` 共通

- いわゆる「宇宙船演算子」 `<=>` と同じようなもの→ Wikipedia: [宇宙船演算子](https://ja.wikipedia.org/wiki/%E5%AE%87%E5%AE%99%E8%88%B9%E6%BC%94%E7%AE%97%E5%AD%90)
- 大小比較する
- ソート、バイナリサーチする場合、max/min などを求める場合、ツリー構造( `TreeSet` , `TreeMap` )を作る際などにも使われる
- 基本的に `equals()` と矛盾しないことが推奨される（もし `equals()` と互換性がない場合には、その旨をjavadocに明記すること）
- 基本的に `Serializable` を実装することが推奨される

## `Comparable` とは

- 自分自身と、別のインスタンスの大小比較をする
- `Comparable` を実装していると「自然な順序付け」ができる

## `Comparator` とは

- 2 つのオブジェクトの大小比較をする

# 便利なメソッド

- `java.util.Collections#reverseOrder()`

引数をとるものと、引数なしのものがあります。
引数をとるものは、指定された `Comparator` の逆順の `Comparator` を返します。
引数をとらないものは、自然な順序の逆順となる `Comparator` を返します。

```java
        // 自然な順序の逆順
		Comparator<String> c1 = Collections.reverseOrder();
        // 指定された Comparator の逆順
		Comparator<String> c2 = Collections.reverseOrder(c1);
```

Java 8 から、同じ目的のメソッドが `Comparator` にも追加されました。

- `java.util.Comparator#reverseOrder()`

前述の引数なしの `Collections#reverseOrder()` と同じ、自然な順序の逆順となる `Comparator` を返します。
`Comparator` 系のメソッドを、`Comparator` インタフェースに集約したものだと思います。

```java
        // 自然な順序の逆順
		Comparator<String> c3= Comparator.reverseOrder();
```

- `java.util.Comparator#reversed()`

前述の引数ありの `Collections#reverseOrder()` のように逆順の `Comparator` を返します。違いは、引数で渡されたインスタンスを対象とするのではなく、インスタンスそのもののメソッドであることです。

```java
        // 自分の逆順
        Comparator<String> c4 = c3.reversed();
```

- `java.util.Comparator#naturalOrder()`

自然な順序の `Comparator` を取得できます。

- `java.util.Comparator#comparing()`

オブジェクトから値を取り出して比較する `Comparator` を生成します。
ラムダあるいはメソッド参照で指定するのが一般的だと思います。

```java
public class Bean {
	private String data1;

	public String getData1() {
		return data1;
	}
}

// ...

    Comparator<Bean> c5 = Comparator.comparing(Bean::getData1);
```

プリミティブ版の `comparingInt()`, `comparingLong()`, `comparingDouble()` などもあります。

- `java.util.Comparator#thenComparing()`

`Comparator` を結合して、1 つ目の `Comparator` の結果が 0 だった場合（等しい場合）に、2 つ目の Comparator の結果を使います。
3 つ以上を結合することもできます。

```java
public class Bean {
	private String data1;
	private String data2;

	public String getData1() {
		return data1;
	}

	public String getData2() {
		return data2;
	}
}

// ...

    Comparator<Bean> c6 = Comparator.comparing(Bean::getData1)
            .thenComparing(Bean::getData2);
```

- `java.util.Comparator#nullsFirst()` , `nullsLast()`

引数で渡した `Comparator` を `null` セーフにします。2 つのメソッドの違いは `null` の項目を `null` 以外の項目の前に持ってくるか( `nullsFirst` )、後にもってくるか( `nullsLast` )です。

- `java.lang.String#CASE_INSENSITIVE_ORDER`

`String` クラスには、英大小文字を無視した大小比較用の `Comparator` が定数として用意されています。

## `Map.Entry` の比較

Java 8 から `Map.Entry` の Key あるいは Value で比較することが簡単にできるようになっています。おそらく Stream などでの使用を想定しているのかと思います。

- `java.util.Map.Entry#comparingByKey()`
- `java.util.Map.Entry#comparingByKey(Comparator<? super K>)`
- `java.util.Map.Entry#comparingByValue()`
- `java.util.Map.Entry#comparingByValue(Comparator<? super K>)`

- `java.util.Objects#compare()`

`Objects` クラスには、オブジェクトの大小チェックができる `compare()` メソッドがあります。`Comparable` を実装しているオブジェクトの比較が可能です。

独自クラスの `Comparable` や `Comparator` を実装する際、メンバー変数の比較をするときなどに便利です。
また、ラムダ式で `Comparator` を作るときにも便利です。

- `java.lang.Integer#compare()` , `java.lang.Long#compare()` など

類似のメソッドは `Boolean`, `Byte`, `Character`, `Short`, `Integer`, `Long`, `Float`, `Double` などのプリミティブ型のラッパークラスにもあります。

# 注意点

## `equals()` と矛盾しないこと

`Comparable` や `Comparator` の実装では、必ずしも `equals()` と矛盾しない結果を出す必要はありません。
実際に標準ライブラリには、`Comparable` を実装していても `equals()` と互換性がない結果を返すものがあります（たとえば `java.math.BigDecimal` ）。

しかし基本は `equals()` と矛盾しない結果を返すことが推奨されます。

これは `TreeSet` や `TreeMap` などいくつかの標準ライブラリが、`Comparable` (や `Comparator`)が 0 を返すことと、`equals()` が `true` を返すことを同一視して処理しているためです。

たとえば `Comparable` を実装するクラスが、メンバ変数として `data1`, `data2`, `data3` を持っており、`equals()` がその 3 つのメンバで判定しているとします。
その場合には（たとえ、ソートする条件としては `data1`, `data2` だけでも十分だったとしても）、`compareTo()` でも同じく 3 つのメンバ変数で判定する必要があるということです。

そうしないと `data1`, `data2` が同じで `data3` が違うものを、キー重複と判断し片方が捨てられてしまうことがあります。

```java
public final class Bean implements Comparable<Bean>, Serializable {

	private static final long serialVersionUID = 1;

	private String data1;
	private String data2;
	private String data3;

	@Override
	public int hashCode() {
		return Objects.hash(data1, data2, data3);
	}

	@Override
	public boolean equals(Object other) {
		if (this == other) {
			return true;
		}
		if (other instanceof Bean) {
			Bean that = (Bean) other;
			return Objects.equals(this.data1, that.data1)
					&& Objects.equals(this.data2, that.data2)
					&& Objects.equals(this.data3, that.data3);
		}
		return false;
	}

	@Override
	public int compareTo(Bean that) {
		Comparator<String> naturalOrder = Comparator.naturalOrder();
		int rc = Objects.compare(data1, that.data1, naturalOrder);
		if (rc == 0) {
			rc = Objects.compare(data2, that.data2, naturalOrder);
			if (rc == 0) {
				rc = Objects.compare(data3, that.data3, naturalOrder);
			}
		}
		return rc;
	}
}
```

## シリアライズ可能とすること

`Comparator` の実装では、同様に基本はシリアライズ可能にすることが推奨されています。

`TreeMap` や `TreeSet` などに `Comparator` を設定したあとに、シリアライズをされるようなことがあっても問題ないようにするためです。

標準ライブラリが生成する `Comparator` は、もちろんシリアライズ可能になっています。
ただし気をつけなくてはならないのは、普通のラムダ式やメソッド参照などは（交差型キャストをしなければ）シリアライズ可能ではないということです。

```java
public final class Bean implements Comparable<Bean>, Serializable {

    private static final long serialVersionUID = 1;
    private static final Comparator<Bean> COMPARATOR = Comparator.comparing(Bean::getData1)
			.thenComparing(Bean::getData2)
			.thenComparing(Bean::getData3);

	private String data1;
	private String data2;
	private String data3;

	public String getData1() {
		return data1;
	}

	public String getData2() {
		return data2;
	}

	public String getData3() {
		return data3;
	}

	@Override
	public int compareTo(Bean that) {
		return COMPARATOR.compare(this, that);
	}
}
```

たとえば上記のように生成した `COMPARATOR` はシリアライズ不可能です。

もし、これを public にして外部から使ってもらった場合、一見問題ないように見えますが、その `COMPARATOR` をシリアライズしようとしたら例外が発生してしまいます。

この例で言えば `COMPARATOR` は `private` のままにしておいて、`Comparator#naturalOrder()` で処理してもらうほうが安全だと思います。

もちろんデータをソートする、大小チェックするなど、その場で操作が完了するような状況で利用するにはシリアライズ不可能なラムダ式でも問題ありません。

## 継承に気をつけること

継承による親子関係があると、`equals()` や大小比較が非常に複雑になります（特に対称性の確保について）。
通常、データを入れるだけの Bean (DTO など) は継承して項目を追加などは不要なことがほとんどだと思います。

その場合、親クラスを `java.lang.Object` として、`final` クラスとして定義することで、そういった難しい問題を確実に避けることができます。

# その他

## 比較結果の見かた

`Comparable` や `Comparator` の戻り値は、正零負の３種類があります。
覚え方は何種類かありますが、以下のように覚えるのが簡単かと思います。

| 結果 | 比較 | 意味 |
|:--:|:--:|:--:|
| 正 | compare(a, b) > 0  |  a > b |
| 非負 | compare(a, b) >= 0  |  a >= b |
| 零 | compare(a, b) ==0 | a == b |
| 非正 | compare(a, b) <= 0  | a <= b |
| 負 | compare(a, b) < 0  | a < b |

- ゼロと比較する
- ゼロは比較演算子の右側に置く
- `compare()` と 0 の間の比較演算子は、a と b の間においたものと考える

## 愚痴

- `Comparator#comparingBoolean()` がないのがちょっと面倒。ラムダ式で書けばいいのだけど。
- nullセーフ＋自然順序や、nullセーフ名＋comparing() など頻発する。毎回書くのは煩雑な気がする
- ラムダは全部、Serializeable にしておけば良いのに…。