【忘備録】Java : 等価判定のequalsのオーバーライドは必要か

## はじめに
[先の記事](https://qiita.com/noytdm1021/items/8ff7a65af25ebda19f08)でも述べているように現在参考書([スッキリわかるJava入門 実践編](https://www.amazon.co.jp/dp/B00O0NIW30/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1))を進めながらJavaを勉強し直しています。(実は既に読み終わっています:blush:)

上記参考書の中でクラスを作成する際には既に備えられているメソッドをオーバーライドして意図しない不具合を引き起こさないようにするように注意喚起をしています。
その一つの例が`equals`メソッドです。

`equals`メソッドは等値ではなく等価であるかどうかを判定してくれるメソッドです。しかし参考書によると、元々備えられているObjectクラスの`equals`メソッドは等価であるかどうかを等値であるかどうかで判定しています。
つまり`equals を呼び出したオブジェクトと equals の引数として渡すオブジェクトが等値であれば2つのオブジェクトは等価だよね？`と当たり前の事を判定している訳です。

## 実装
上記の判定方法を裏付ける下記のコードをご覧下さい:sunglasses:
Personクラスのインスタンスを作成後、`ArrayList`に追加して、追加したインスタンスのメンバと等価のメンバを持つ新しいインスタンスで削除対象を指定しています。

```instance.java
import java.util.ArrayList;
import java.util.List;

class Person {
    public String name;
    Person(String name){
        this.name = name;
    }
}
public class instance {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("太郎"));
        System.out.println("追加後(person) : " + personList.size());
        personList.remove(new Person("太郎"));
        System.out.println("削除後(person) : " + personList.size());
    }
}

```

一見、うまくいきそうですが結果は`ArrayList`の要素は削除されません。

```結果.java
        追加後(person) : 1
        削除後(person) : 1
```

確かにインスタンスのメンバは等価関係ですがインスタンスのメモリの番地が違えばメンバのメモリの番地も違う筈なので`等値で無いので等価では無い`と判断されたそうです。故に元々備わっているオブジェクトクラスの`equals`メソッドは`false`判定となっているようです。

そこで冒頭でも言及しているように`equals`メソッドをオーバーライドして上記の不具合を修正していきます。

```instance.java
class Man {
    public String name;
    Man(String name){
        this.name = name;
    }
    public boolean equals(Object o){
        if(o == this) return true;
        if(o == null) return false;
        if(!(o instanceof Man)) return false;
        Man man = (Man) o ;
        if(!(this.name.equals(man.name))) return false;
        return true;
    }
}
public class instance {
    public static void main(String[] args) {
        List<Man> manList = new ArrayList<Man>();
        manList.add(new Man("太郎"));
        System.out.println("追加後(man) : " + manList.size());
        manList.remove(new Man("太郎"));
        System.out.println("削除後(man) : " + manList.size());
    }
}
```
今度は`Man`クラスの中に`equals`メソッドを予め、定義しています。
具体的には比較元と比較対象のオブジェクトの型が同一でメンバ(`name`)が等価なら`true`を返すようにしています。

```結果.java
        追加後(man) : 1
        削除後(man) : 0
```

はい、上記のように意図するように動いてくれるようになりました。

しかし、上記のようにいちいちオーバーライドするのもかなり面倒です。
別にオーバーライドしなくとも下記のように`for`で回しながら該当要素を取り除いてしまえば十分なのでは？と考えてしまいます:thinking:

```instancce.java
import java.util.ArrayList;
import java.util.List;

class Person {
    public String name;
    Person(String name){
        this.name = name;
    }
}
public class instance {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("太郎"));

        System.out.println("for文削除前(person) : " + personList.size());
        for(int i=0; i<personList.size(); i++){
            Person person = personList.get(i);
            String name = person.name;
            if(name.equals("太郎")){
                personList.remove(person);
            }
        }
        System.out.println("for文削除後(person) : " + personList.size());
    }
}

```
```結果.java
        for文削除前(person) : 1
        for文削除後(person) : 0
```

あとは`equals`メソッドの記述が冗長でいちいち書くのが面倒臭いと言う人には、`commons-lang`と言うライブラリの`EqualsBuilder`を使えば`全てのメンバが等価ならインスタンスも等価とみなす`判定をしてくれます。
今回は試していませんが記述方法は下記です。

```equalsBuilder.java
import org.apache.commons.lang3.builder.*;

class Man {
    public String name;
    Man(String name){
        this.name = name;
    }
    public boolean equals(Object o){
        return EqualsBuilder.reflectionEquals(this,o);
    }
}
```

## おまけ
今回、`for文`で要素を指定して削除した方法を載せましたがこれを`拡張for文`で回すと`ConcurrentModificationException`と言うエラーが発生します。
このエラーは`iterator`が作成された際に作成対象のコレクションなどが`add`や`remove`等によって操作されると返されます。

詳細は下記リンクたちを参考に。。。(書くのが面倒。。。)
https://qiita.com/sig_Left/items/eebea3f88a16dcfa2983
https://teratail.com/questions/16901