Java에서 Equals 및 hashCode를 재정의할 때 고려해야 할 문제는 무엇입니까?

StackOverflow https://stackoverflow.com/questions/27581

문제

재정의할 때 고려해야 할 문제/함정 equals 그리고 hashCode?

도움이 되었습니까?

해결책

이론(언어 변호사 및 수학적인 경향이 있는 사람을 위한):

equals() (javadoc)은 동등 관계를 정의해야 합니다. 반사적, 대칭, 그리고 전이적).또한, 일관된 (객체가 수정되지 않은 경우 동일한 값을 계속 반환해야 합니다.)뿐만 아니라, o.equals(null) 항상 false를 반환해야 합니다.

hashCode() (javadoc) 또한 있어야 합니다. 일관된 (객체가 다음과 같이 수정되지 않은 경우 equals(), 동일한 값을 계속 반환해야 합니다).

그만큼 관계 두 가지 방법 중 하나는 다음과 같습니다.

언제든지 a.equals(b), 그 다음에 a.hashCode() 다음과 같아야 합니다. b.hashCode().

실제로:

하나를 재정의하면 다른 것도 재정의해야 합니다.

계산에 사용하는 것과 동일한 필드 세트를 사용하십시오. equals() 계산하기 hashCode().

우수한 도우미 클래스 사용 EqualsBuilder 그리고 해시코드 빌더 ~로부터 아파치 커먼즈 랭 도서관.예:

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, 해시맵, 해시테이블, 또는 WeakHashMap, 컬렉션에 넣은 키 개체의 hashCode()가 개체가 컬렉션에 있는 동안 변경되지 않는지 확인하세요.이를 보장하는 방탄 방법은 키를 변경할 수 없게 만드는 것입니다. 다른 이점도 있습니다.

다른 팁

Hibernate와 같은 ORM(Object-Relationship Mapper)을 사용하여 지속되는 클래스를 다루는 경우, 이것이 이미 불합리하게 복잡하다고 생각하지 않았다면 주목할 만한 몇 가지 문제가 있습니다!

지연 로드된 객체는 하위 클래스입니다.

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은 일반적으로 getter를 사용하여 지연 로드된 객체를 강제로 로드합니다.이는 다음을 의미합니다. person.name 될거야 null 만약에 person 게으른 로드임에도 불구하고 person.getName() 강제로 로드하고 "John Doe"를 반환합니다.내 경험상 이런 일이 더 자주 발생합니다. hashCode() 그리고 equals().

ORM을 다루는 경우 항상 getter를 사용하고 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;

분명히 이것은 잘못된 것입니다.

대칭 조건을 보장하려는 경우.b=a이고 Liskov 대체 원칙 호출인 경우 a=b 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 () 메서드를 올바르게 구현하려면 어떻게해야합니까?

요약 :

그의 저서 Effective Java Programming Language Guide (Addison-Wesley, 2001 ), Joshua Bloch는 "동등 계약을 유지하면서 인스턴스화 가능한 클래스를 확장하고 측면을 추가 할 수있는 방법은 없습니다."라고 주장합니다. Tal은 동의하지 않습니다.

그의 해결책은 두 가지 방법으로 다른 비대칭 blindlyEquals ()를 호출하여 equals ()를 구현하는 것입니다. blindlyEquals ()는 서브 클래스에 의해 재정의되고 equals ()는 상속되며 결코 재정의되지 않습니다.

예 : 라코 디스

Liskov 대체 원칙 이 적용되는 경우 equals ()는 상속 계층간에 작동해야합니다. 만족합니다.

구아바 라이브러리를 추천하는 사람이 없다는 사실에 여전히 놀랐습니다. 라코 디스

수퍼 클래스에는 java.lang.Object라는 두 가지 메소드가 있습니다.맞춤 개체로 재정의해야합니다. 라코 디스

동등한 개체는 동일하다면 동일한 해시 코드를 생성해야하지만 동일하지 않은 개체는 별개의 해시 코드를 생성 할 필요가 없습니다. 라코 디스

더 많은 정보를 원하시면이 링크를 http : // www로 확인하십시오..javaranch.com / journal / 2002 / 10 / equalhash.html

이것은 또 다른 예입니다. http : //java67.blogspot.com / 2013 / 04 / example-of-overriding-equals-hashcode-compareTo-java-method.html

즐기세요!@. @

멤버 평등을 확인하기 전에 클래스 평등을 확인하는 몇 가지 방법이 있으며 두 가지 모두 올바른 상황에서 유용하다고 생각합니다.

  1. instanceof 연산자를 사용합니다.
  2. this.getClass().equals(that.getClass())를 사용합니다.

    final equals 구현 또는 equals에 대한 알고리즘을 규정하는 인터페이스를 구현할 때 # 1을 사용합니다 (java.util 컬렉션 인터페이스와 같이 (obj instanceof Set) 또는 구현중인 인터페이스로 확인하는 올바른 방법).등호가 대칭 속성을 깨기 때문에 대체 될 수있는 경우 일반적으로 잘못된 선택입니다.

    옵션 # 2를 사용하면 같음을 재정의하거나 대칭을 깨지 않고도 클래스를 안전하게 확장 할 수 있습니다.

    클래스가 Comparable 인 경우 equalscompareTo 메서드도 일관성이 있어야합니다.다음은 Comparable 클래스의 equals 메소드에 대한 템플릿입니다. 라코 디스

동등한 경우 동등의 비밀 작성자 : Angelika Langer .나는 그것을 매우 좋아합니다.그녀는 또한 자바의 제네릭 에 대한 훌륭한 FAQ입니다.그녀의 다른 기사는 여기 ( "Core Java"로 스크롤)를 참조하십시오.Part-2 및 "혼합 유형 비교"에서도 계속됩니다.재미있게 읽으세요!

equals () 메서드는 두 개체의 동등성을 결정하는 데 사용됩니다.

10의 int 값은 항상 10과 같습니다. 그러나이 equals () 메서드는 두 객체의 동등성에 관한 것입니다. 객체라고하면 속성이 있습니다. 평등을 결정하기 위해 이러한 속성이 고려됩니다. 동등성을 결정하기 위해 모든 속성을 고려할 필요는 없으며 클래스 정의 및 컨텍스트와 관련하여 결정할 수 있습니다. 그런 다음 equals () 메서드를 재정의 할 수 있습니다.

equals () 메서드를 재정의 할 때마다 항상 hashCode () 메서드를 재정의해야합니다. 그렇지 않다면 어떻게 될까요? 애플리케이션에서 해시 테이블을 사용하면 예상대로 작동하지 않습니다. hashCode는 저장된 값의 동일성을 결정하는 데 사용되므로 키에 대해 올바른 해당 값을 반환하지 않습니다.

주어진 기본 구현은 Object 클래스의 hashCode () 메서드는 객체의 내부 주소를 사용하여 정수로 변환하여 반환합니다. 라코 디스

예제 코드 출력 : 라코 디스

논리적으로 우리는 :

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

하지만 그 반대는 아니요 !

내가 발견 한 한 가지 문제점은 두 개체가 서로에 대한 참조를 포함하는 곳입니다 (한 가지 예는 모든 자식을 가져 오는 부모에 대한 편리한 방법이있는 부모 / 자식 관계).
예를 들어 Hibernate 매핑을 할 때 이런 종류의 일이 상당히 일반적입니다.

해시 코드에 관계의 양쪽 끝을 포함하거나 같음 테스트를 수행하면 StackOverflowException으로 끝나는 재귀 루프가 발생할 수 있습니다.
가장 간단한 해결책은 메소드에 getChildren 컬렉션을 포함하지 않는 것입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top