LoginSignup
2
0

More than 3 years have passed since last update.

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

Posted at

はじめに

先の記事でも述べているように現在参考書(スッキリわかるJava入門 実践編)を進めながら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が作成された際に作成対象のコレクションなどがaddremove等によって操作されると返されます。

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

2
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0