はじめに
先の記事でも述べているように現在参考書(スッキリわかるJava入門 実践編)を進めながらJavaを勉強し直しています。(実は既に読み終わっています)
上記参考書の中でクラスを作成する際には既に備えられているメソッドをオーバーライドして意図しない不具合を引き起こさないようにするように注意喚起をしています。
その一つの例がequals
メソッドです。
equals
メソッドは等値ではなく等価であるかどうかを判定してくれるメソッドです。しかし参考書によると、元々備えられているObjectクラスのequals
メソッドは等価であるかどうかを等値であるかどうかで判定しています。
つまりequals を呼び出したオブジェクトと equals の引数として渡すオブジェクトが等値であれば2つのオブジェクトは等価だよね?
と当たり前の事を判定している訳です。
実装
上記の判定方法を裏付ける下記のコードをご覧下さい
Personクラスのインスタンスを作成後、ArrayList
に追加して、追加したインスタンスのメンバと等価のメンバを持つ新しいインスタンスで削除対象を指定しています。
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
の要素は削除されません。
追加後(person) : 1
削除後(person) : 1
確かにインスタンスのメンバは等価関係ですがインスタンスのメモリの番地が違えばメンバのメモリの番地も違う筈なので等値で無いので等価では無い
と判断されたそうです。故に元々備わっているオブジェクトクラスのequals
メソッドはfalse
判定となっているようです。
そこで冒頭でも言及しているようにequals
メソッドをオーバーライドして上記の不具合を修正していきます。
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
を返すようにしています。
追加後(man) : 1
削除後(man) : 0
はい、上記のように意図するように動いてくれるようになりました。
しかし、上記のようにいちいちオーバーライドするのもかなり面倒です。
別にオーバーライドしなくとも下記のようにfor
で回しながら該当要素を取り除いてしまえば十分なのでは?と考えてしまいます
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());
}
}
for文削除前(person) : 1
for文削除後(person) : 0
あとはequals
メソッドの記述が冗長でいちいち書くのが面倒臭いと言う人には、commons-lang
と言うライブラリのEqualsBuilder
を使えば全てのメンバが等価ならインスタンスも等価とみなす
判定をしてくれます。
今回は試していませんが記述方法は下記です。
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