[java] Java에서 equals 및 hashCode를 재정의 할 때 고려해야 할 사항은 무엇입니까?

재정의 할 때 고려해야 할 사항 equals과 함정은 무엇입니까 hashCode?



답변

이론 (언어 변호사와 수학적으로 기울어 진) :

equals()( javadoc )는 동등성 관계를 정의해야합니다 ( 반사적 , 대칭 적 , 전 이적 이어야 함 ). 또한 일관성 이 있어야합니다 (개체를 수정하지 않은 경우 계속 같은 값을 반환해야 함). 또한 o.equals(null)항상 false를 반환해야합니다.

hashCode()( javadoc )도 일관성이 있어야합니다 (객체가로 수정되지 않은 경우 equals()계속 동일한 값을 반환해야 함).

두 방법 의 관계 는 다음과 같습니다.

때마다 a.equals(b), 다음 a.hashCode()과 같은 동일해야합니다 b.hashCode().

실제로:

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

당신이 계산에 사용하는 필드의 동일한 세트를 사용하여 equals()계산에 hashCode().

Apache Commons Lang 라이브러리 의 탁월한 헬퍼 클래스 EqualsBuilderHashCodeBuilder 를 사용하십시오 . 예를 들면 :

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();
    }
}

또한 기억하십시오 :

HashSet , LinkedHashSet , HashMap , Hashtable 또는 WeakHashMap 과 같은 해시 기반 Collection 또는 Map을 사용 하는 경우, 오브젝트가 콜렉션에있는 동안 콜렉션에 넣은 키 오브젝트의 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올바르게 사용하는 것이 유일한 방법입니다.

게으른로드 된 오브젝트에는 널 필드가 있습니다.

ORM은 일반적으로 게터를 사용하여 지연로드 된 객체를 강제로로드합니다. 이 수단 person.name이 될 것입니다 null경우 person,로드 게으른 경우에도 person.getName()강제로로드 및 반환 “홍길동”. 내 경험에 의하면,이 더 자주 작물 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()위해 hashCodegetId()플러스 getName()에 대한 (단지 편집증에 대한) equals(). 에 대한 “충돌”의 위험이있는 경우에는 hashCode()괜찮지 만 결코 괜찮습니다 equals().

hashCode() 속성의 변경되지 않는 하위 집합을 사용해야합니다. equals()


답변

에 대한 설명 obj.getClass() != getClass()입니다.

이 진술은 equals()상속이 비우호적 인 결과입니다 . JLS (Java 언어 사양)는 그렇다면 A.equals(B) == trueB.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 인 경우 a = b이고 Liskov 대체 원리는 예를 들어서 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 () 메소드를 올바르게 구현하는 방법을 확인하십시오.

요약:

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

그의 해결책은 두 가지 방법으로 다른 비대칭 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);
    }
}

Liskov 대체 원칙 을 만족 하려면 equals ()가 상속 계층 구조에서 작동해야합니다 .


답변

아무도 구아바 도서관을 추천하지 않았다는 것에 여전히 놀랐습니다.

 //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라는 두 가지 메소드가 있습니다. 우리는 그것들을 커스텀 객체로 재정의해야합니다.

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

즐기세요! @. @


답변

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

  1. instanceof연산자를 사용하십시오 .
  2. 사용하십시오 this.getClass().equals(that.getClass()).

final등호 구현 에서 # 1을 사용 하거나 등호 알고리즘을 규정하는 인터페이스를 구현할 때 ( java.util컬렉션 인터페이스 등 (obj instanceof Set)구현중인 인터페이스 를 확인하는 올바른 방법 ). 대칭 특성을 손상시키기 때문에 equals를 무시할 수있는 경우 일반적으로 나쁜 선택입니다.

옵션 # 2를 사용하면 등호를 무시하거나 대칭을 위반하지 않고 클래스를 안전하게 확장 할 수 있습니다.

클래스가 또한 Comparable인 경우 equalsand compareTo메소드도 일관성이 있어야합니다. Comparable클래스 의 equals 메소드에 대한 템플릿은 다음과 같습니다 .

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

}