617

オーバーライドするときに考慮しなければならない問題/落とし穴equalsそしてhashCode

11 답변


1383

理論(言語弁護士と数学的に傾いている人向け):

equals()Javadoc)等価関係を定義しなければならない(それは反射的左右対称、そして推移的)さらに、それはする必要があります一貫している(オブジェクトが変更されていない場合は、同じ値を返し続ける必要があります)。さらに、o.equals(null)常にfalseを返す必要があります。

hashCode()Javadocもする必要があります一貫している(オブジェクトが以下の点で変更されていない場合equals()同じ値を返し続ける必要があります。

関係2つの方法の間にあります:

いつでもa.equals(b)それからa.hashCode()と同じでなければなりませんb.hashCode()

実際には:

一方を上書きする場合は、もう一方を上書きする必要があります。

計算に使用したものと同じ一連のフィールドを使用するequals()計算しますhashCode()

優れたヘルパークラスを使うEqualsBuilderそしてHashCodeBuilderから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();
    }
}

また覚えています:

ハッシュベースを使用している場合コレクションまたは地図といったハッシュセットLinkedHashSetハッシュマップハッシュ表または弱ハッシュマップオブジェクトがコレクション内にある間は、コレクションに入れるキーオブジェクトのhashCode()が変更されないようにしてください。これを確実にするための防弾方法はあなたの鍵を不変にすることです。他にも利点があります


  • appendSuper()についての追加ポイント:スーパークラスの同等の振る舞いを継承したい場合に限り、hashCode()およびequals()でそれを使用する必要があります。たとえば、Objectから直接派生した場合、デフォルトではすべてのObjectが異なるため、意味がありません。 - Antti Kissaniemi
  • Eclipseに2つのメソッドを生成させることができます。 hashCode()とequals()を生成します。 - Rok Strniša
  • NetBeansについても同様です。developmentality.wordpress.com/2010/08/24/… - seinecle
  • @Darthenius Eclipseが生成するequalsはgetClass()を使用しているため、場合によっては問題が発生する可能性があります(Effective Java item 8を参照)。 - AndroidGecko
  • 次の事実を考えると、最初のnullチェックは必要ありません。instanceof最初のオペランドがNULLの場合はfalseを返します(これも有効なJavaです)。 - izaban

282

HibernateのようなObject-Relationship Mapper(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()


  • @ Johannes Brodwall:わかりませんSaving an object will change it's statehashCode帰らなければならないintだから、あなたはどのように使うのですかgetName()?あなたの例を挙げてもらえますかhashCode - jimmybondy
  • @ jimmybondy:getNameは使用可能なhashCodeを持つStringオブジェクトを返します - mateusz.fiolka

82

についての説明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))それが本来あるべきように、結果は真実を出します。

これはすべて非常に良さそうに見えますが、両方のクラスを使用しようとするとどうなるかを調べます。

A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;

明らかに、これは間違っています。

対称条件を確保したい場合b = aでLiskovの代入原理が呼び出す場合、a = bsuper.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()


  • (obj.getClass()!= this.getClass()&& obj.getClass()。isInstanceのように、equalsをこのように対称にすることができます(スーパークラスオブジェクトをサブクラスオブジェクトと比較する場合は、必ずサブクラスのequalsを使用してください)。 (this))obj.equals(this)を返します。 - pihentagy
  • @pihentagy - 実装クラスがequalsメソッドをオーバーライドしない場合、スタックオーバーフローが発生します。楽しくない。 - Ran Biron
  • あなたはスタックオーバーフローを得ることができませんでした。 equalsメソッドがオーバーライドされていない場合は、同じコードをもう一度呼び出しますが、再帰の条件は常にfalseになります。 - Jacob Raihle
  • @pihentagy:2つの異なる派生クラスがある場合、それはどのように動作しますか?もしThingWithOptionSetAaに等しいThingすべての追加オプションにデフォルト値があり、同様にThingWithOptionSetBそれからそれは可能であるべきですThingWithOptionSetAaと等しいThingWithOptionSetB両方のオブジェクトの基本以外のすべてのプロパティがデフォルトと一致する場合に限りますが、そのテスト方法がわかりません。 - supercat
  • これに伴う問題は、それが推移性を破るということです。追加した場合B b2 = new B(1,99)それからb.equals(a) == trueそしてa.equals(b2) == trueしかしb.equals(b2) == false。 - nickgrim

43

継承に適した実装については、Tal Cohenのソリューションを調べてください。equals()メソッドを正しく実装する方法

概要:

彼の本の中で効果的なJavaプログラミング言語ガイド(Addison-Wesley、2001)、Joshua Blochは、「インスタンス化可能なクラスを拡張し、同等の契約を維持しながらアスペクトを追加する方法はまったくない」と主張している。タルは同意しません。

彼の解決策は、別の非対称blindlyEquals()を両方向に呼び出すことによって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()は継承階層全体で機能する必要があります。リスコフ代用原則満足することです。


  • ここで説明されているcanEqualメソッドを見てみましょう - 同じ原則で両方の解決策が機能しますが、canEqualでは同じフィールドを2回比較しないでください(上記では、p.x == this.xは両方向でテストされます)。artima.com/lejava/articles/equality.html - Blaisorblade
  • Point.equals()はPointにキャストされている必要があります(オブジェクトに対してblindlyEquals()を呼び出そうとしています)。それは意図的だったかもしれませんが(方法を強調するために)。 - Kevin
  • いずれにせよ、これは良い考えだとは思わない。これは、Equals契約を不必要に混乱させます - aとbの2つのPointパラメータをとる人は、a.getX()== b.getX()とa.getY()== b.getYの可能性を意識する必要があります。 ()はtrueでも構いませんが、a.equals(b)とb.equals(a)は両方ともfalseです(一方がColorPointの場合)。 - Kevin
  • 基本的にはこれはif (this.getClass() != o.getClass()) return falseしかし、派生クラスが修正を煩わす場合にのみfalseを返すという点で柔軟です。その通りですか? - Aleksandr Dubinsky

31

まだ誰もこれのために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.util.Objects.hash()とjava.util.Objects.equals()はJava 7(2011年にリリース)の一部であるため、これにはGuavaは必要ありません。 - herman
  • もちろん、OracleはJava 6のパブリックアップデートを提供していないので、これを避ける必要があります(2013年2月からそうなっています)。 - herman
  • きみのthisthis.getDate()何も意味しません(雑然以外) - Steve Kuo
  • あなたの" not instanceof"式には追加の括弧が必要です。if (!(otherObject instanceof DateAndPattern)) {。 hernanとSteve Kuoに同意します(ただし、個人的な好みの問題ですが)。ただし+1です。 - Amos M. Carpenter

26

スーパークラスには、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

楽しむ! @。


  • 申し訳ありませんが、hashCodeメソッドに関するこのステートメントを理解していません。equals()よりも多くの変数を使用する場合は無効です。しかし、もっと多くの変数を使ってコーディングすると、私のコードはコンパイルされます。それはなぜ合法ではないのですか? - Adryr83

18

メンバーの平等をチェックする前にクラスの平等をチェックする方法はいくつかありますが、どちらも正しい状況では便利だと思います。

  1. 使用instanceofオペレーター。
  2. つかいますthis.getClass().equals(that.getClass())

私は#1をfinalequals実装、またはequalsのアルゴリズムを規定するインタフェースを実装するときjava.utilコレクションのインターフェース - 確認する正しい方法(obj instanceof Set)あるいは、あなたが実装しているどんなインターフェースでも。これは、対称性を損なうため、equalsをオーバーライドできる場合は一般に悪い選択です。

オプション#2を指定すると、等号をオーバーライドしたり対称性を破ったりせずにクラスを安全に拡張できます。

あなたのクラスもComparableequalsそして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;
  }

}


  • これに対して+1。 getClass()もinstanceofも万能薬ではありません、そしてこれは両方に近づく方法の良い説明です。 equals()を使用する代わりにthis.getClass()== that.getClass()を実行しない理由があるとは思わないでください。 - Paul Cantrell
  • これには1つ問題があります。アスペクトを追加せず、equalsメソッドをオーバーライドしない匿名クラスは、たとえそれらが等しくなるべきであってもgetClassチェックに失敗します。 - Steiny
  • @Steiny異なる種類のオブジェクトが同じであるべきかどうかは私には明らかではありません。私は、インターフェイスのさまざまな実装を共通の匿名クラスとして考えています。あなたの前提を裏付ける例を挙げていただけますか。 - erickson
  • MyClass a = new MyClass(123); MyClass b = new MyClass(123){//メソッドをオーバーライド}; // this.getClass()。equals(that.getClass())を使用する場合、// a.equals(b)はfalseです。 - Steiny
  • @スティニーそうですね。ほとんどの場合にそうであるように、特にメソッドが追加されるのではなくオーバーライドされる場合は特にそうです。上記の私の例を考えてください。見つからなかった場合final、 そしてそのcompareTo()ソート順を逆にするためにmethodがオーバーライドされました。サブクラスとスーパークラスのインスタンスは等しいと見なすべきではありません。これらのオブジェクトがツリー内で一緒に使用されていた場合、「等しい」キーが使用されていました。によるとinstanceof実装が見つからない可能性があります。 - erickson

15

等しい場合は、平等の秘密によってアンジェリカランガー。私はそれが大好きです。彼女はまたについての素晴らしいFAQです。Javaのジェネリックス。彼女の他の記事を見るここに( "Core Java"までスクロールしてください)そこで彼女はまたPart-2と "混合型比較"を続けます。それらを読んで楽しんでください!


11

equals()メソッドは、2つのオブジェクトが等しいかどうかを判断するために使用されます。

10のint値は常に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


7

論理的には

a.getClass().equals(b.getClass()) && a.equals(b)a.hashCode() == b.hashCode()

しかしではない逆に!


6

私が見つけた1つの落とし穴は、2つのオブジェクトがお互いへの参照を含む場合です(1つの例は、すべての子を取得するために親に便利メソッドを持つ親子関係です)。

たとえば、Hibernateマッピングを実行する場合、この種のことはかなり一般的です。

hashCodeまたはequalsテストに関係の両端を含めると、StackOverflowExceptionで終わる再帰ループに入る可能性があります。

最も簡単な解決策は、メソッドにgetChildrenコレクションを含めないことです。


  • ここでの基礎となる理論は、属性集合体そして仲間オブジェクトののアソシエーション参加してはいけませんequals()。気が狂った科学者が私の写しを作成したならば、私たちは同等であろう。しかし、私たちには同じ父親がいないでしょう。 - Raedwald

リンクされた質問


関連する質問

最近の質問