Effective Java(第3版) 項目10 equalsをオーバーライドするときは一般契約に従う 

equalsをオーバーライドする場合に守るべき内容について、かなり詳細に記載しています。

# 記載内容
## equalsのオーバライドが不要な場合
以下の条件のいずれかに該当する場合、equalsをオーバーライドしないのが正しい。

* クラスの個々のインスタンスが本質的に一意(Threadクラス等)
* 「論理的等価性」の検査を提供する必要が無い(正規表現のPattern等)
* スーパークラスでオーバーライドしており、その振る舞いで問題ない（AbstractSetとSet等)
* クラスがprivate、パッケージプライベートであり、equalsが呼ばれないことが確実

## equalsのオーバライドが必要な場合
一般に、値クラス(Integer、Stringの様に、値を表現するクラス)の場合は、
equalsのオーバーライドが必要になる。
ただし、個々の値に対して1つのインスタンスしか存在しないクラスは
オーバーライド不要である。
(Objectのequalsメソッドが十分機能するため)

## equalsメソッドの一般契約
equalsメソッドは、Objectクラスの使用に記載されている一般契約(反射性、対称性、推移性、一貫性、nullとの比較)に従う必要が有る。
(一般契約についての説明は[Object.equals()](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/Object.html#equals-java.lang.Object-)の通りなのでここでは割愛)

### 反射性
この性質を意図せず破ってしまうことは少ない

### 対称性
この性質は、意図せずに破ってしまう可能性がある。
例として、大文字小文字を区別しない文字列クラスCaseInsensitiveStringを考えた場合、
equalsメソッドを以下の様に実装すると、対称性を破っている。

```Java:CaseInsensitiveString.java
private final String s; // 内部保持する文字列

@Override
public boolean equals(Object o) {
    if (o instanceof CaseInsensitiveString)
        return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
    if (o instanceof String)
        return s.equalsIgnoreCase((String) o);
    return false;
}
```

この場合、以下の様になる

```java
CaseInsensitiveString cis = new CaseInsensitiveString("Foo");
String s = "foo";

System.out.println(cis.equals(s));            // true
System.out.println(s.equals(cis));            // false 対称性を破っている
System.out.println(List.of(cis).contains(s)); // 対称性を破っているため、結果は不明。OpenJDKではたまたまfalseになる
```
この問題を解決するには、CaseInsensitiveStringのequalsメソッドからStringクラスとうまく機能しようとしている部分を取り除くしかない。

### 推移性
この性質は、スーパークラスに新たな値要素を付加するサブクラスを考えると、容易に破ってしまう。
Point(点)とそのサブクラスColorPoint(色付きの点)の比較を考えた場合に、
以下のコードで、ColorPoint.equalsが色を無視した比較を行うと、対称性は守っているが、推移性を破ってしまう。

```Java:ColorPoint.java
@Override
public boolean equals(Object o) {
    if (!(o instanceof Point))
       return false;

    Point point = (Point) o;
    return x == point.x && y == point.y;
}
```

```Java:ColorPoint.java
@Override
public boolean equals(Object o) {
    if (!(o instanceof Point))
       return false;
    if (!(o instanceof ColorPoint))
       return o.equlas(this);  // 色を無視した比較

    // oはColorなので完全な比較
    return super.equals(o) && ((ColorPoint) o).color == color;
}
```

対称性、推移性は以下の様になる。

```java
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);

System.out.println(p1.equals(p2)); // true
System.out.println(p2.equals(p1)); // true 対称性は守れている
System.out.println(p2.equals(p3)); // true
System.out.println(p1.equals(p3)); // false 推移性が守れていない
```

これは、オブジェクト指向言語における同値関係の基本的な問題であり、
**インスタンス化可能なクラスを拡張して、equals契約を守ったまま値要素を追加する方法ない。**

この問題の回避方法は項目18「継承よりもコンポジションを選ぶ」ことである。
ColorPointがPointを拡張するのをやめ、ColorPointにPoint、Colorのフィールドを持たせ、
以下の様な、ColorPointの位置のPointを返すビューメソッドを返すようにすれば良い。

```Java:ColorPoint.java
  public Point asPoint() {
    return point;
  }
```

### 整合性
この性質は、信頼性のない資源に依存するequalsメソッドを記載した場合に、破ってしまう可能性がある。
例として、java.net.URLクラスのequalsメソッドは、整合性を破っており、呼び出すたびに結果が異なる可能性がある。
これは、URLクラスのequalsメソッドは、URLに関連付けられたIPアドレスの比較に依存しており、
ホストのIPアドレスへの変換は常に成功するとは限らず、同じ結果になることも保証されないためである。
しかし、互換性を維持するために、この誤った振る舞いを変更することが出来ずにいる。

### 非null性(名前が無いために命名)
o.equals(null)==trueとなる、誤った実装が行われることは考えづらいが、
誤ってNullPointerExceptionがスローされることは考えられる。
一般契約では、引数がnullの場合にNullPointerExceptionのスローは認められていないため、falseを返す必要が有る。

# 考察
長い項目ですが、記載されている内容はequalsに関する基本的な内容であり、
特に一般契約については理解しておく必要が有ります。

## equalsのオーバライド有無
実はequalsを実装するかで悩ましいのは、既存のクラスにequalsメソッドを追加してしまってよいか、
というケースだと思っています。
(この項目の記載とは少しずれますが・・・)

特に、既存のクラスでjUnitが不十分だった場合に、比較を行いたいクラス(主にメソッドの戻り値)にequalsを追加したくなりますが、
追加した場合に、他の処理に影響が無いか、というのが確認しづらいです。
(さらに、equalsを追加する場合はhashcodeも実装するわけで、問題ないかが不安になります・・・)
やはり、値クラスに対しては、使用有無に関係なく、equalsとhashcodeは初期から実装しておくべきと思います。

使用しないコードは記載するべきではない、という意見もあると思いますが、
意図せずObject.equalsが使用されているかもしれず、equalsを実装して良いか分からない、
という状況になるより良いはずです。

## 一般契約
一般契約で主に問題になるのは、継承と推移性の問題と思います。
やはり、値クラスは継承するべきではない、ということになると思います。
(継承用に定義した、abstractの値クラスの継承は除きますが)
さらに一般化して、継承用に定義した抽象クラス以外は、継承を行うべきではないということになると思います。

## equalsの実装
現在は、IntelliJ、Eclipse等のIDEで、equalsメソッドとhashcodeメソッドをセットで生成してくれますので、
これに従うのが良いと思います。
もし、何らかの理由で特殊な比較を行う場合は、その旨をコメントで記載するべきです。

さらに、RecordがJavaに導入されれば、値クラスに対してequalsの記載が不要にあり、
equals、hashcodeに対して、決まり切ったコードを記載する煩わしさが減るはずです。