2012-01-14 22 views
31

2つのクラスは、両方とも他のオブジェクトへの参照を含むように定義されています。彼らは(これが簡略化され、私の本当のドメインモデルクラスAは、Bのリストが含まれており、各Bは、バック親Aへの参照を持っている中で)次のようになり:Javaで循環参照を持つオブジェクトのequalsとhashCodeを実装する

public class A { 

    public B b; 
    public String bKey; 

    @Override 
    public int hashCode() { 
     final int prime = 31; 
     int result = 1; 
     result = prime * result + ((b == null) ? 0 : b.hashCode()); 
     result = prime * result + ((bKey == null) ? 0 : bKey.hashCode()); 
     return result; 
    } 
    @Override 
    public boolean equals(Object obj) { 
     if (this == obj) 
      return true; 
     if (obj == null) 
      return false; 
     if (!(obj instanceof A)) 
      return false; 
     A other = (A) obj; 
     if (b == null) { 
      if (other.b != null) 
       return false; 
     } else if (!b.equals(other.b)) 
      return false; 
     if (bKey == null) { 
      if (other.bKey != null) 
       return false; 
     } else if (!bKey.equals(other.bKey)) 
      return false; 
     return true; 
    } 
} 

public class B { 

    public A a; 
    public String aKey; 

    @Override 
    public int hashCode() { 
     final int prime = 31; 
     int result = 1; 
     result = prime * result + ((a == null) ? 0 : a.hashCode()); 
     result = prime * result + ((aKey == null) ? 0 : aKey.hashCode()); 
     return result; 
    } 
    @Override 
    public boolean equals(Object obj) { 
     if (this == obj) 
      return true; 
     if (obj == null) 
      return false; 
     if (!(obj instanceof B)) 
      return false; 
     B other = (B) obj; 
     if (a == null) { 
      if (other.a != null) 
       return false; 
     } else if (!a.equals(other.a)) 
      return false; 
     if (aKey == null) { 
      if (other.aKey != null) 
       return false; 
     } else if (!aKey.equals(other.aKey)) 
      return false; 
     return true; 
    } 
} 

hashCodeequalsのEclipseによって生成されましたどちらのオブジェクトでもequalsまたはhashCodeメソッドを呼び出すと、他のオブジェクトのequalsおよびhashCodeメソッドが呼び出されるため、StackOverflowErrorという結果になるという問題があります。たとえば、次のプログラムは、上記の目的を使用してStackOverflowErrorで失敗します:

public static void main(String[] args) { 

     A a = new A(); 
     B b = new B(); 
     a.b = b; 
     b.a = a; 

     A a1 = new A(); 
     B b1 = new B(); 
     a1.b = b1; 
     b1.a = a1; 

     System.out.println(a.equals(a1)); 
    } 

をその後、私に知らせてください。この方法では、円形の関係で定義されたドメインモデルを有する本質的に何か問題がある場合。限り、これはかなり一般的なシナリオですが、私が言うことができる限り、正しい?

この場合、hashCodeequalsを定義するベストプラクティスは何ですか。私はequalsメソッドのすべてのフィールドをオブジェクトに真の深い等価比較するようにしたいが、私はこの問題をどのようにすることができないのか分かりません。ありがとう!

+4

は、なぜあなたは、親への参照を持っている子どもたちが必要なのでしょうか?これはあなたの人生をシリアライゼーションに関して非常に複雑にするつもりです – I82Much

+2

私は従来のドメインモデルを使っています。私のシリアライゼーションは、本質的に子供の親の関係を取り除き、逆直列化の関係を修正します。循環参照はドメインモデルで共通のものではありませんか?基礎となるDB関係は、JBossツールによって循環参照付きのJPAオブジェクトにリバースエンジニアリングされたOneToManyです。 – Tom

答えて

5

I82のコメントに同意すると、Bが親を参照することを避けるべきです。情報の重複は通常は問題につながりますが、あなたの場合はそうする必要があります。

あなたはBで親参照を残していても、限りハッシュコードを懸念しているとして、あなたは完全に親の参照を無視して、唯一のハッシュコードをビルドするためにB内部変数を使用する必要があります。

Aはコンテナに過ぎず、その値はコンテンツの内容によって完全に決まります。これは含まれるBの値であり、ハッシュキーも同様です。

Aが順序付けられていないセットである場合、B値(またはBハッシュコード)から構築するハッシュコードがいくつかの順序に依存しないように注意する必要があります。たとえば、含まれるBのハッシュコードを何らかの順序で加算して乗算してハッシュコードを構築する場合は、合計/乗算の結果を計算する前に順序を増やして最初に順序を決定する必要があります。同様に、A.equals(o)は、B(順序なしセットの場合)の順序に依存してはなりません。あなただけの親の参照を無視してBのハッシュコードを固定し、その後Ajava.util.Collectionを、使用している場合Collection sがデフォルト(順序かどうか)によって良いハッシュコードを持っているので、自動的に有効なAハッシュコードを与えること

注意。

+0

私はこれがあなたの言うような情報の重複であるかどうかはよくわかりません。 Bさんの親がAから参照されているため、Bの親がAであることを推測することができます.Bのハンドルを持つだけで、参照されているAについて何か知る必要がありますか?その情報は、BがAへの参照を持っていない場合は存在しません。 – Tom

+0

これはあなたが何をしているかによって異なります。多分あなたは本当にBの親を見つける必要があります。しかし、一般的には、コードを避けて設計するべきです。あなたがそれを避けることができないならば、矛盾した状態にならないように非常に注意する必要があります。 – toto2

+0

私は、二重リンクリストにはデータの重複があり、特定の状況で効率を上げるのに役立つと思います。しかし、APIは所有権の両側が常に同時に変更されるため、一貫性のない状態になることはありません。 – toto2

3

一般的なモデルでは、ほとんどのエンティティに一意のIDがあります。このIDは、さまざまなユースケースで役立ちます(特に、データベースの復旧/参照)。 IIUCでは、bKeyフィールドはそのようなユニークなIDであると考えられています。したがって、このようなエンティティを比較するための一般的な方法は、自分のIDを比較することです:

@Override 
public boolean equals(Object obj) { 
    if (obj == null) 
     return false; 
    if (!getClass().equals(obj.getClass())) 
     return false; 
    return this.bKey.equals(((B) obj).bKey); 
} 


@Override 
public int hashCode() { return bKey.hashCode(); } 

あなたが求めることができる:「2つのBのオブジェクトが(そのフィールドの値が異なっている)同じIDが異なる状態を持っている場合、何が起こります」。あなたのコードでは、こうしたことが起こらないようにする必要があります。これは、equals()またはhashCode()の実装方法にかかわらず問題になります。これは、基本的に、システム内に同じエンティティの2つの異なるバージョンがあり、どちらが正しいかを知ることができないためです。

+0

これは私にとって理にかなっています。しかし、私の場合は、カスタムシリアライザjunitテストのための包括的なequalsメソッドが必要でした。私の単体テストでは、オブジェクトが正しい方法でシリアル化され、逆シリアル化されていることを確認したいので、すべてのフィールドをテストしました。実際には私のドメインモデルはおそらく私のequalsメソッドがオブジェクトIDをテストすることでうまくいくでしょう。 – Tom

+0

これはベストプラクティスの質問に対処するのに役立ちます。 – Tom

-2

まず、Equals()GetHashCode()を無効にしてもよろしいですか?ほとんどのシーンエイリアスでは、デフォルトの参照平等でうまくいくはずです。

しかし、そうしないとしましょう。あなたが望む適切な平等セマンティクスは何か?

例えば者は、各AタイプBgetBフィールドを持っており、各BはタイプAgetAフィールドを持っているとしましょう。 a1a2を2つのAオブジェクトにすると、同じフィールドと同じgetB(同じメモリアドレスと同じ)b1があります。 a1a2は等しいですか? b1.getAa1(同じメモリアドレスと同じですが)と同じですが、a2と同じではないとします。 a1a2は同じと見なしますか?

そうでない場合は、何も上書きしないで、デフォルトの参照平等を使用してください。 Aには、getB要素に依存しない(ただし、他のフィールドに依存する)int GetCoreHashCode()関数があるとします。 Bには、getA要素に依存しない(しかし、他のフィールドに依存する)関数int GetCoreHashCode()があります。今度はint GetHashCode()の機能をAに、this.GetCoreHashCode()getB.GetCoreHashCode()に、そしてBに、それぞれ似せています。

+0

これはおそらく動作しますが、ドメインモデルのベストプラクティスと再帰的な問題の具体的な解決策を探しています。 – Tom

0

equalsという2つのフレーバーを持つことができます。つまり、オーバーライドはObject.equalsで、再帰に適しています。再帰的な等価性チェックは、AかBのどちらかをとります。他ののクラスです。これは、再帰的等価を呼び出すオブジェクトです。 this.equalsの代わりにそれを呼び出す場合は、nullを渡します。例えば:だから

A { 
    ... 
    @Override 
    public boolean equals(Object obj) { 
     // check for this, null, instanceof... 
     A other = (A) obj; 
     return recursiveEquality(other, null); 
    } 

    // package-private, optionally-recursive equality 
    boolean recursiveEquality(A other, B onBehalfOf) { 
     if (onBehalfOf != null) { 
      assert b != onBehalfOf; 
      // we got here from within a B.equals(..) call, so we just need 
      // to check that our B is the same as the one that called us. 
     } 
     // At this point, we got called from A.equals(Object). So, 
     // need to recurse. 
     else if (b == null) { 
      if (other.b != null) 
       return false; 
     } 
     // B has a similar structure. Call its recursive-aware equality, 
     // passing in this for the onBehalfOf 
     else if (!b.recursiveEquality(other.b, this)) 
      return false; 

     // check bkey and return 
    } 
} 

は、A.equals以下:this.b != null場合

  1. A.equals呼び出し `recursiveEquality(ヌルotherA、)
    1. 、我々は呼び出す第三のif-elseブロック、で終わりますb.recursiveEquality(other.b, this)
      1. B.recursiveEqualityの場合、に最初に i f-elseブロックは、Aが私たちに渡されたのと同じものである(つまり、循環参照が壊れていない)ことを単にアサートします。
      2. aKeyをチェックして終了します(あなたのインバリアントに応じて、手順3)で何が起きたかに基づいて何かを主張する。B.recursiveEquality戻り
    2. 私たちは似たとおそらくbKeyをチェックすることにより、A.recursiveEqualityを終えるには
  2. A.equalsはここ
+0

これはおそらく動作しますが、ドメインモデルのベストプラクティスと再帰的な問題の具体的な解決策を探しています。 – Tom

関連する問題