JavaでequalsとhashCodeをオーバーライドする場合、どのような問題を考慮する必要がありますか?
質問
オーバーライドするときに考慮する必要がある問題/落とし穴 equals
そして hashCode
?
解決
理論 (語学専門家と数学に興味のある人向け):
equals()
(Javadoc) は等価関係を定義する必要があります ( 反射的, 対称的な, 、 そして 推移的)。さらに、それは、 一貫性のある (オブジェクトが変更されていない場合は、同じ値を返し続ける必要があります)。さらに、 o.equals(null)
常に false を返す必要があります。
hashCode()
(Javadoc) もなければなりません 一貫性のある (オブジェクトが次の点で変更されていない場合 equals()
, 、同じ値を返し続ける必要があります)。
の 関係 2 つの方法の間には次のものがあります。
いつでも
a.equals(b)
, 、 それからa.hashCode()
と同じでなければなりませんb.hashCode()
.
実際には:
一方をオーバーライドする場合は、もう一方もオーバーライドする必要があります。
計算に使用するのと同じフィールドのセットを使用します equals()
計算します hashCode()
.
優れたヘルパー クラスを使用する イコールビルダー そして ハッシュコードビルダー から Apache Commons Lang 図書館。例:
public class Person {
private String name;
private int age;
// ...
@Override
public int hashCode() {
return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
append(name).
append(age).
toHashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
if (obj == this)
return true;
Person rhs = (Person) obj;
return new EqualsBuilder().
// if deriving: appendSuper(super.equals(obj)).
append(name, rhs.name).
append(age, rhs.age).
isEquals();
}
}
次のことも覚えておいてください。
ハッシュベースを使用する場合 コレクション または 地図 のような ハッシュセット, リンクされたハッシュセット, ハッシュマップ, ハッシュ表, 、 または ウィークハッシュマップ, 、コレクションに入れるキー オブジェクトの hashCode() が、オブジェクトがコレクション内にある間は決して変更されないことを確認してください。これを確実に行う確実な方法は、キーを不変にすることです。 他にも利点があります.
他のヒント
Hibernate などのオブジェクト リレーションシップ マッパー (ORM) を使用して永続化されるクラスを扱っている場合、これがすでに不当に複雑だと思っていない場合は、注目に値する問題がいくつかあります。
遅延ロードされたオブジェクトはサブクラスです
ORM を使用してオブジェクトが永続化されている場合、多くの場合、データ ストアからのオブジェクトの読み込みが早すぎることを避けるために動的プロキシを処理することになります。これらのプロキシは、独自のクラスのサブクラスとして実装されます。この意味はthis.getClass() == o.getClass()
戻ります false
. 。例えば:
Person saved = new Person("John Doe");
Long key = dao.save(saved);
dao.flush();
Person retrieved = dao.retrieve(key);
saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy
ORM を扱っている場合は、次を使用します。 o instanceof Person
正しく動作する唯一のものです。
遅延ロードされたオブジェクトには null フィールドがあります
ORM は通常、ゲッターを使用して、遅延ロードされたオブジェクトを強制的にロードします。この意味は person.name
になるだろう null
もし person
たとえ遅延ロードされていても、 person.getName()
強制的にロードし、「John Doe」を返します。私の経験では、これは次の場合によく発生します。 hashCode()
そして equals()
.
ORM を扱っている場合は、必ずゲッターを使用し、フィールド参照は使用しないようにしてください。 hashCode()
そして equals()
.
オブジェクトを保存するとその状態が変わります
永続オブジェクトでは、多くの場合、 id
オブジェクトのキーを保持するフィールド。このフィールドは、オブジェクトが最初に保存されるときに自動的に更新されます。ID フィールドを使用しないでください hashCode()
. 。しかし、あなたはそれを使うことができます equals()
.
私がよく使うパターンは、
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
しかし:含めることはできません getId()
で hashCode()
. 。そうすると、オブジェクトが永続化されると、 hashCode
変化します。オブジェクトが HashSet
, 、二度と見つかることはありません。
私の中で Person
たとえば、おそらく使用します getName()
のために hashCode
そして getId()
プラス getName()
(妄想のためだけに) equals()
. 。多少の「衝突」のリスクがあっても大丈夫 hashCode()
, 、しかし決して大丈夫ではありません equals()
.
hashCode()
からのプロパティの変更しないサブセットを使用する必要があります。 equals()
についての説明 obj.getClass() != getClass()
.
この声明は次の結果です。 equals()
継承が不親切であること。JLS (Java 言語仕様) では、次のように指定しています。 A.equals(B) == true
それから B.equals(A)
も戻らなければなりません true
. 。オーバーライドするクラスを継承するステートメントを省略した場合 equals()
(そしてその動作を変更する) と、この仕様が壊れてしまいます。
ステートメントが省略された場合に何が起こるかを示す次の例を考えてみましょう。
class A {
int field1;
A(int field1) {
this.field1 = field1;
}
public boolean equals(Object other) {
return (other != null && other instanceof A && ((A) other).field1 == field1);
}
}
class B extends A {
int field2;
B(int field1, int field2) {
super(field1);
this.field2 = field2;
}
public boolean equals(Object other) {
return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other));
}
}
やってる new A(1).equals(new A(1))
また、 new B(1,1).equals(new B(1,1))
結果は、当然のことながら true を返します。
これは非常に良いように見えますが、両方のクラスを使用しようとするとどうなるかを見てください。
A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;
明らかに、これは間違っています。
対称性を確保したい場合。a=b if b=a およびリスコフ置換原理呼び出し super.equals(other)
の場合だけでなく B
例ですが、後で確認してください A
実例:
if (other instanceof B )
return (other != null && ((B)other).field2 == field2 && super.equals(other));
if (other instanceof A) return super.equals(other);
else return false;
これは出力します:
a.equals(b) == true;
b.equals(a) == true;
どこで、もし a
の参考ではありません B
, 、その場合、それはクラスの参照である可能性があります A
(拡張するため)、この場合は呼び出します super.equals()
あまりにも.
継承に適した実装については、Tal Cohen のソリューションを確認してください。 equals() メソッドを正しく実装するにはどうすればよいですか?
まとめ:
彼の本の中で 効果的な Java プログラミング言語ガイド (Addison-Wesley、2001)、Joshua Blochは、「均等契約を維持しながら、即座のクラスを拡張してアスペクトを追加する方法はない」と主張しています。 TALは同意しません。
彼の解決策は、別の非対称のブラインドリー Equals() を双方向で呼び出すことによって、equals() を実装することです。BlindlyEquals() はサブクラスによってオーバーライドされ、equals() は継承され、オーバーライドされることはありません。
例:
class Point {
private int x;
private int y;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return (p.x == this.x && p.y == this.y);
}
public boolean equals(Object o) {
return (this.blindlyEquals(o) && o.blindlyEquals(this));
}
}
class ColorPoint extends Point {
private Color c;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return (super.blindlyEquals(cp) &&
cp.color == this.color);
}
}
以下の場合、equals() は継承階層全体で機能する必要があることに注意してください。 リスコフ置換原理 満足することです。
これに guava ライブラリを推奨する人が誰もいなかったことには、今でも驚きました。
//Sample taken from a current working project of mine just to illustrate the idea
@Override
public int hashCode(){
return Objects.hashCode(this.getDate(), this.datePattern);
}
@Override
public boolean equals(Object obj){
if ( ! obj instanceof DateAndPattern ) {
return false;
}
return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate())
&& Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern());
}
スーパークラスにはjava.lang.Objectとして2つのメソッドがあります。それらをカスタム オブジェクトにオーバーライドする必要があります。
public boolean equals(Object obj)
public int hashCode()
等しいオブジェクトは、等しい限り同じハッシュ コードを生成する必要がありますが、等しくないオブジェクトは異なるハッシュ コードを生成する必要はありません。
public class Test
{
private int num;
private String data;
public boolean equals(Object obj)
{
if(this == obj)
return true;
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
// object must be Test at this point
Test test = (Test)obj;
return num == test.num &&
(data == test.data || (data != null && data.equals(test.data)));
}
public int hashCode()
{
int hash = 7;
hash = 31 * hash + num;
hash = 31 * hash + (null == data ? 0 : data.hashCode());
return hash;
}
// other methods
}
さらに詳しく知りたい場合は、このリンクをチェックしてください http://www.javaranch.com/journal/2002/10/equalhash.html
これは別の例ですが、http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
楽しむ!@.@
メンバーの平等性をチェックする前にクラスの平等性をチェックするにはいくつかの方法があり、適切な状況ではどちらも役立つと思います。
- 使用
instanceof
オペレーター。 - 使用
this.getClass().equals(that.getClass())
.
私は#1を使用します final
等号の実装、または等号のアルゴリズムを規定するインターフェイスを実装する場合 ( java.util
コレクションインターフェイス - を使用してチェックする正しい方法 (obj instanceof Set)
または実装しているインターフェイス)。対称性が損なわれるため、equals がオーバーライドされる可能性がある場合、これは一般に悪い選択です。
オプション #2 を使用すると、equals をオーバーライドしたり対称性を壊したりすることなく、クラスを安全に拡張できます。
あなたのクラスも Comparable
, 、 equals
そして compareTo
方法も一貫している必要があります。以下は、equals メソッドのテンプレートです。 Comparable
クラス:
final class MyClass implements Comparable<MyClass>
{
…
@Override
public boolean equals(Object obj)
{
/* If compareTo and equals aren't final, we should check with getClass instead. */
if (!(obj instanceof MyClass))
return false;
return compareTo((MyClass) obj) == 0;
}
}
同等の場合は、調べてください 対等の秘密 による アンジェリカ・ランガー. 。私はそれが大好きです。彼女は、次のことについての素晴らしい FAQ でもあります。 Java のジェネリックス. 。彼女の他の記事を見る ここ (「Core Java」までスクロールダウン)、そこで彼女はパート 2 と「混合型の比較」についても続けます。楽しんで読んでください!
equals() メソッドは、2 つのオブジェクトが等しいかどうかを判断するために使用されます。
int 値 10 は常に 10 と等しいためです。しかし、このequals()メソッドは2つのオブジェクトが等しいことを意味します。オブジェクトというとき、それはプロパティを持ちます。等しいかどうかを決定するには、これらの特性が考慮されます。等しいかどうかを判断するためにすべてのプロパティを考慮する必要はなく、クラス定義とコンテキストに基づいて判断できます。その後、equals() メソッドをオーバーライドできます。
equals() メソッドをオーバーライドするときは常に hashCode() メソッドをオーバーライドする必要があります。そうでない場合はどうなりますか?アプリケーションでハッシュテーブルを使用すると、期待どおりに動作しません。hashCode は保存されている値が等しいかどうかを判断するために使用されるため、キーに対応する正しい値は返されません。
デフォルトの実装では、Object クラスの hashCode() メソッドがオブジェクトの内部アドレスを使用し、それを整数に変換して返します。
public class Tiger {
private String color;
private String stripePattern;
private int height;
@Override
public boolean equals(Object object) {
boolean result = false;
if (object == null || object.getClass() != getClass()) {
result = false;
} else {
Tiger tiger = (Tiger) object;
if (this.color == tiger.getColor()
&& this.stripePattern == tiger.getStripePattern()) {
result = true;
}
}
return result;
}
// just omitted null checks
@Override
public int hashCode() {
int hash = 3;
hash = 7 * hash + this.color.hashCode();
hash = 7 * hash + this.stripePattern.hashCode();
return hash;
}
public static void main(String args[]) {
Tiger bengalTiger1 = new Tiger("Yellow", "Dense", 3);
Tiger bengalTiger2 = new Tiger("Yellow", "Dense", 2);
Tiger siberianTiger = new Tiger("White", "Sparse", 4);
System.out.println("bengalTiger1 and bengalTiger2: "
+ bengalTiger1.equals(bengalTiger2));
System.out.println("bengalTiger1 and siberianTiger: "
+ bengalTiger1.equals(siberianTiger));
System.out.println("bengalTiger1 hashCode: " + bengalTiger1.hashCode());
System.out.println("bengalTiger2 hashCode: " + bengalTiger2.hashCode());
System.out.println("siberianTiger hashCode: "
+ siberianTiger.hashCode());
}
public String getColor() {
return color;
}
public String getStripePattern() {
return stripePattern;
}
public Tiger(String color, String stripePattern, int height) {
this.color = color;
this.stripePattern = stripePattern;
this.height = height;
}
}
コード出力例:
bengalTiger1 and bengalTiger2: true
bengalTiger1 and siberianTiger: false
bengalTiger1 hashCode: 1398212510
bengalTiger2 hashCode: 1398212510
siberianTiger hashCode: –1227465966
論理的には次のようになります。
a.getClass().equals(b.getClass()) && a.equals(b)
⇒ a.hashCode() == b.hashCode()
しかし ない 逆に!
私が見つけた落とし穴の 1 つは、2 つのオブジェクトに相互参照が含まれている場合です (1 つの例として、親にすべての子を取得するための便利なメソッドを備えた親子関係があります)。
この種のことは、たとえば Hibernate マッピングを行う場合に非常に一般的です。
hashCode または等しいテストに関係の両端を含めると、StackOverflowException で終了する再帰ループに陥る可能性があります。
最も簡単な解決策は、メソッドに getChildren コレクションを含めないことです。