2012-01-14 5 views
31

둘 다 다른 객체에 대한 참조를 포함하도록 두 클래스를 정의했습니다.Java에서 순환 참조가있는 객체에 대해 equals 및 hashCode 구현

hashCodeequals
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; 
    } 
} 

는 이클립스에 의해 생성 된 : (내 실제 도메인 모델 클래스 A가 B의 목록을 포함에 각 B는 부모 A를 다시 참조가이 단순화) 그들은이 유사 A와 B 두 필드를 모두 사용합니다. 두 객체 모두에서 equals 또는 hashCode 메서드를 호출하면 StackOverflowError이 반환되는데, 그 이유는 둘 다 다른 객체의 equalshashCode 메서드를 호출하기 때문입니다. 예를 들어, 다음 프로그램은 상기 목적을 사용하여 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 관계는 순환 참조가있는 JPA 객체로 JBoss 도구에 의해 리버스 엔지니어링 된 OneToMany입니다. – Tom

답변

5

I82의 의견에 동의합니다. B가 부모를 참조하는 것을 피해야한다는 것은 동의합니다. 정보 복제는 일반적으로 문제 만 일으키지 만, 귀하의 경우에는 그렇게해야 할 수도 있습니다.

당신은 당신이 완전히 부모의 참조를 무시하고 단지 해시 코드를 구축 B진정한 내부 변수를 사용해야까지 해시 코드에 관한 한, B의 부모 참조를 떠날 경우에도 마찬가지입니다.

A은 단지 컨테이너이며 그 값은 포함 된 값인의 내용 인 내용에 따라 완전히 결정됩니다. 따라서 해시 키도 마찬가지입니다.

A이 순서가 지정되어 있지 않은 경우 B 값 (또는 B 해시 코드)에서 작성하는 해시 코드가 일부 주문에 종속되지 않도록 매우주의해야합니다. 예를 들어 해시 코드가 포함 된 해시 코드에 시퀀스를 추가하고 곱하여 빌드하는 경우 합계/곱하기 결과를 계산하기 전에 순서를 증가시켜 해시 코드를 먼저 정렬해야합니다. 마찬가지로 A.equals(o)B (순서가 설정되지 않은 경우)의 순서에 의존해서는 안됩니다.당신은 단지 부모의 참조를 무시하여 B의 해시 코드를 수정 한 후 A 내에서 java.util.Collection을 사용하는 경우 Collection의 기본 (순서 없음)에 의해 좋은 해시 코드를 갖고 있기 때문에 자동으로 유효한 A 해시 코드를 줄 것이다

주 .

+0

저는 이것이 당신이 말하는 정보 중복이란 것을 잘 모릅니다. 하나는 엄격하게 A를 다루지 않을 수 있습니다. 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(); } 

당신은 요청할 수 있습니다 : "어떤 일이 발생이 B 객체가 같은 ID 만 다른 상태 (자신의 필드 값이 다릅니다)가있는 경우". 코드는 그런 일이 일어나지 않도록해야합니다. 이것은 equals() 또는 hashCode()을 구현하는 방법에 관계없이 문제가됩니다. 이는 본질적으로 시스템에 동일한 엔티티의 두 가지 버전이 있으며 올바른지를 알 수 없기 때문입니다.

+0

이것은 나에게 의미가 있습니다. 필자의 경우, 커스텀 시리얼 라이저 junit 테스트를위한 포괄적 인 equals 메소드가 필요했다. 유닛 테스트에서 객체가 올바른 방법으로 직렬화되고 직렬화되었는지 확인하기 위해 모든 필드를 테스트했습니다. 실제로 내 도메인 모델은 아마도 equals 메소드가 객체 ID를 테스트하도록 만들 것입니다. – Tom

+0

이것은 모범 사례 문제를 해결하는 데 도움이됩니다. – Tom

-2

우선 Equals()GetHashCode()을 덮어 쓰시겠습니까? 대부분의 scenearios에서는 기본 참조 동등성을 유지해야합니다.

하지만 그렇지 않다고 가정 해 봅시다. 원하는 평등 의미론은 무엇입니까?

예를 들어의 각 A 유형 BgetB 필드가 생각한 각 B 유형 AgetA 필드가 있습니다. a1a2을 두 개의 A 개체로하고 동일한 필드와 동일한 getB (동일한 메모리 주소와 동일) b1을 사용하십시오. a1a2이 같은가요? b1.getAa1 ("동일한 메모리 주소"와 같음)과 동일하지만 a2과 같지 않다고 가정합니다. a1a2을 여전히 같습니까?

그렇지 않은 경우 아무 것도 재정의하지 않고 기본 참조 동등성을 사용하십시오. 요소에 의존하지 않고 에 함수가 있다고 가정합니다 (다른 필드에 따라 다름). B에 getA 요소에 종속되지 않지만 다른 필드에 종속되는 int GetCoreHashCode() 함수가 있습니다. 이제 int GetHashCode()의 기능을 A에 따라 this.GetCoreHashCode()getB.GetCoreHashCode(), 그리고 B에 따라 완료하면 완료됩니다.

+0

그러나이 방법을 사용하면 도메인 모델 모범 사례와 재귀 문제에 대한 특정 솔루션을 비교할 수 있습니다. – Tom

0

equals의 두 가지 맛을 나타낼 수 있습니다. 즉, 재정의는 Object.equals이며 재귀에 더 적합합니다. 재귀 적 동등성 검사는 A 또는 B 중 어느 것이 든 다른 클래스 중 하나를 취합니다. 이는 사용자가 재귀 적 등호를 호출하는 객체입니다. this.equals을 대신하여 전화하는 경우 null을 전달합니다. A.equals 다음 그래서

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 
    } 
} 

: : 예를 들어

  1. A.equals 통화`this.b != null, 우리는 세 번째 IF-다른 블록에 결국
    1. recursiveEquality (널 otherA)를 호출하는 b.recursiveEquality(other.b, this)
      1. B.recursiveEquality에서 첫 번째 f-else 블록은 A이 우리에게 전달 된 것과 동일하다고 단언합니다 (순환 참조가 손상되지 않았 음).
      2. aKey을 확인하여 B.recursiveEquality을 마무리합니다 (귀하의 불변량에 따라, 3 단계에서 일어난 일을 기반으로 무언가를 주장하는 것).B.recursiveEquality 반환
    2. 우리가 비슷한으로 가능 bKey를 확인하여 A.recursiveEquality 마무리는
  2. A.equals 여기
+0

그러나 이것은 잘 작동 할 것입니다. 그러나 도메인 모델 모범 사례와 재귀 적 문제에 대한 특정 솔루션을 비교했습니다. – Tom

관련 문제