Java オブジェクトの hashCode() と equals() を簡単に実装する
同一性の比較とハッシュコード
Java クラスの equals() メソッドはデフォルトでは、同一インスタンスであるかを比較しているだけなので、論理的に同一であるかを比較するためには、equals() メソッドをオーバーライドする必要がある。
また、equals() メソッドをオーバーライドする場合、あわせてhashCode()メソッドもオーバーライドする必要がある。
hashCode()は、equals()が真になるインスタンスは必ず、同じになる値を返すことで、一致するオブジェクトを探しやすくする役目を果たす。例えば、学籍番号を、百番台で分けることにより、そのグループの中を探せば効率がよくなるように。
学籍番号(ID) | 学籍番号グループ(ハッシュコード) |
001 ~ 099 | 0 |
101 ~ 199 | 1 |
201 ~ 299 | 2 |
上記の例のように、学籍番号グループ(ハッシュコード) ごとにグループ(バケツ)を作成し、その中に学籍番号が表す実態(インスタンス)を入れておけば、例えば、学籍番号 222 のインスタンスを見つけるには、学籍番号グループ(ハッシュコード) 2 のバケツの中だけ探せばよい。
ハッシュコードとは、あるデータが与えられたときに、常に同じ値を返す(ハッシュ関数)を経てえられた値のことなので、極端に言うと、常に一定の値を返しても、”間違い"ではないが、効率が悪いのは、上記の例で言えば、222 のインスタンスを見つけるために、全校生徒を先頭から探さなければいけないことから、想像つくだろう。
Javaクラスに実装する、ハッシュコードは、HashMap など、インスタンス管理の基本になるので、値がばらけて格納された方が効率がよかったりと作成の仕方によって、効率が変わってくる。
なーんて、理屈はわかっていても、具体的にはどう実装したらいいんだよ~
という御仁には、Effective Java 第2版 (The Java Series) を一読することをお勧めする。
一部要約すると、
equals をオーバーライドするときは一般契約に従う
一般契約
契約 | 内容 |
反射的 | x.equals(x) は true |
対称的 | y.equals(x) が true の場合のみ、x.equals(y) も true |
推移的 | x.equals(y) 、y.equals(z) が true ならば、x.equals(z) も true |
整合的 | 情報が変更されなければ、x.equals(y) の結果は不変 |
抽象化の恩恵をあきらめずに、インスタンス化可能なクラスを拡張して、equals 契約を守ったまま、値要素を追加する方法はない。
equals をオーバーライドする時は、常に hashCode をオーバーライドする
- ゼロではない定数(たとえば17)を result(int型)に保存
- 意味のあるフィールドに対して、次を行う
- boolean なら (f?1:0)
- byte,char,short,int なら、(int)f
- long なら、(int)(f^(f>>32))
- float なら、Float.floatToIntBits(f)
- double なら、Double.doubleToLongBits(f) を行い、long と同様の変換
- 参照なら、オブジェクトのhashCode() null なら 0
- 配列なら、各要素を フィールドとして扱う
- result = 31 * result + c // c=計算されたハッシュコード
public int hashCode() { int result = 17; result = 31 * result + foo; result = 31 * result + bar; return result; }
equals() 、hashCode() をオーバーライドする簡単な方法
というようなことで、equals()をオーバーライドする必要がある場合などは、Effective Java のメモを確認しながら実装してきた。。。今日までは。
しかし、な、なんと
Eclipse でJavaクラスを選択し、コンテキストメニューから、Source – Generate hashCode() and equals() を実行すると。。。
@Override private Long id; private String name; private String email; private String phoneNumber; public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((email == null) ? 0 : email.hashCode()); result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((phoneNumber == null) ? 0 : phoneNumber.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Member other = (Member) obj; if (email == null) { if (other.email != null) return false; } else if (!email.equals(other.email)) return false; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (phoneNumber == null) { if (other.phoneNumber != null) return false; } else if (!phoneNumber.equals(other.phoneNumber)) return false; return true; }
こんな感じで、両メソッドが、自動生成されるやないかーーーい。
しらんかった。。。
他にも、Constructor using fields とかあるし。。。
いつもは、アクセッサー作成したり、toString() 実装したり指癖というか条件反射でやっていたので、どんなことが出来るかあまり顧みてなかったなー
反省。
Java 実装の指針を与えてくれる良書!おすすめ!