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


617

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

답변:


1439

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

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 ()가 절대 변경되지 않도록하십시오. 이를 보장하는 방탄 방법은 키를 불변으로 만드는 것인데 다른 이점도 있습니다.


12
appendSuper ()에 대한 추가 요점 : 수퍼 클래스의 동등 동작을 상속하려는 경우에만 hashCode () 및 equals ()에서 사용해야합니다. 예를 들어 Object에서 직접 파생하는 경우 모든 Object가 기본적으로 다르기 때문에 아무런 의미가 없습니다.
Antti Kissaniemi

312
Eclipse가 두 가지 메소드 (Source> Generate hashCode () 및 equals ())를 생성하도록 할 수 있습니다.
Rok Strniša

27
Netbeans의 경우도 마찬가지입니다 : developmentality.wordpress.com/2010/08/24/…
seinecle

6
@Darthenius Eclipse 생성 equals는 getClass ()를 사용하여 경우에 따라 문제를 일으킬 수 있습니다 (유효한 Java 항목 8 참조)
AndroidGecko

7
instanceof번째 피연산자가 null 인 경우 false 를 반환 한다는 사실을 고려하면 첫 번째 null 검사는 필요하지 않습니다 (Effective Java 다시).
izaban

295

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


2
@Johannes Brodwall : 이해가 안 Saving an object will change it's state돼요! hashCode반환해야 int하므로 어떻게 사용 getName()하시겠습니까? 당신의 예를들 수 있습니까hashCode
jimmybondy

@jimmybondy : getName은 사용할 수있는 hashCode를 가진 String 객체를 반환합니다.
mateusz.fiolka

85

에 대한 설명 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() 에도 호출 합니다.


2
(obj.getClass ()! = this.getClass () && obj.getClass (). isInstance (this)와 같은 방법으로 등식을 대칭으로 만들 수 있습니다 (수퍼 클래스 객체를 서브 클래스 객체와 비교하는 경우 항상 서브 클래스의 등호를 사용하십시오). ) return obj.equals (this);
pihentagy

5
@pihentagy-그런 다음 구현 클래스가 equals 메서드를 재정의하지 않으면 stackoverflow가 발생합니다. 재미 없어.
Ran Biron

2
스택 오버플로가 발생하지 않습니다. equals 메소드를 재정의하지 않으면 동일한 코드를 다시 호출하지만 재귀 조건은 항상 거짓입니다!
Jacob Raihle

@ pihentagy : 두 개의 다른 파생 클래스가 있으면 어떻게 작동합니까? A는 경우 ThingWithOptionSetAA와 동일 할 수 있습니다 Thing모든 추가 옵션이 기본 값을 가지고 있고, 마찬가지로에 대한 것을 제공 ThingWithOptionSetBA를 위해, 다음이 가능해야한다 ThingWithOptionSetA동등한 비교 될 수있는 ThingWithOptionSetB두 개체의 모든 비 기본 특성은 기본값과 일치하는 경우에만 있지만, 어떻게 테스트하는지 모르겠습니다.
supercat

7
이것의 문제는 그것이 전이성을 파괴한다는 것입니다. 당신은 추가하면 B b2 = new B(1,99)다음 b.equals(a) == truea.equals(b2) == true하지만 b.equals(b2) == false.
nickgrim

46

상속 친화적 인 구현을 위해서는 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 ()가 상속 계층 구조에서 작동해야합니다 .


10
여기에 설명 된 canEqual 메소드를 살펴보십시오. 동일한 원칙으로 두 솔루션이 모두 작동하지만 canEqual을 사용하면 동일한 필드를 두 번 비교하지 않습니다 (위의 px == this.x는 양방향으로 테스트 됨) : artima.com /lejava/articles/equality.html
Blaisorblade

2
어쨌든, 나는 이것이 좋은 생각이라고 생각하지 않습니다. Equals 계약을 불필요하게 혼란스럽게 만듭니다. 두 개의 Point 매개 변수 a와 b를 취하는 사람은 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

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

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

23
java.util.Objects.hash () 및 java.util.Objects.equals ()는 Java 7 (2011 년에 릴리스)의 일부이므로 Guava가 필요하지 않습니다.
herman

1
물론, Oracle은 더 이상 Java 6에 대한 공개 업데이트를 제공하지 않기 때문에 피해야합니다 (2013 년 2 월 이후).
herman

6
당신 thisthis.getDate()의미는 어수선하지 않습니다
Steve Kuo

1
"인스턴스가 아님"표현식에는 추가 대괄호가 필요합니다 if (!(otherObject instanceof DateAndPattern)) {.. 헤르 난과 스티브 쿠오 (단, 개인 취향의 문제임)에 동의하지만 그럼에도 불구하고 +1.
Amos M. Carpenter

26

수퍼 클래스에는 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

즐기세요! @. @


죄송하지만 hashCode 메서드에 대한이 문장을 이해하지 못합니다 : equals ()보다 많은 변수를 사용하는 경우 합법적이지 않습니다. 그러나 더 많은 변수로 코딩하면 코드가 컴파일됩니다. 왜 합법적이지 않습니까?
Adryr83

19

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

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

}

1
이것을 위해 +1. getClass () 나 instanceof는 만병 통치약이 아니며, 두 가지 모두에 접근하는 방법에 대한 좋은 설명입니다. equals ()를 사용하는 대신 this.getClass () == that.getClass ()를하지 말아야 할 이유가 있다고 생각하지 마십시오.
Paul Cantrell

이것에는 한 가지 문제가 있습니다. aspect를 추가하지 않거나 equals 메소드를 재정의하지 않는 익명 클래스는 동일해야하지만 getClass 검사에 실패합니다.
steinybot 2016 년

@Steiny 다른 유형의 객체가 동일해야한다는 것은 분명하지 않습니다. 인터페이스의 다른 구현을 공통 익명 클래스로 생각하고 있습니다. 전제를 뒷받침 할 모범을 보여 줄 수 있습니까?
erickson 2016 년

MyClass a = new MyClass (123); MyClass b = new MyClass (123) {// 일부 메소드 재정의}; // this.getClass (). equals (that.getClass ())를 사용할 때 a.equals (b)는 false입니다.
steinybot

1
@Steiny 맞습니다. 대부분의 경우, 특히 메소드를 추가하는 대신 재정의하는 경우가 그렇습니다. 위의 예를 고려하십시오. 이 아니었다면 final, 그리고 compareTo()방법은 정렬 순서를 반대로 오버라이드 (override)하고, 서브 클래스와 슈퍼 클래스의 인스턴스는 동일한 것으로 간주되어서는 안된다. 이러한 객체를 트리에서 함께 사용하면 instanceof구현 에 따라 "동일한"키를 찾지 못할 수 있습니다.
erickson


11

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

int 값 10은 항상 10과 같습니다. 그러나이 equals () 메소드는 두 객체의 동등성에 관한 것입니다. 우리가 객체를 말할 때 속성이 있습니다. 평등을 결정하기 위해 이러한 속성이 고려됩니다. 동등성을 결정하고 클래스 정의 및 컨텍스트와 관련하여 결정할 수있는 모든 특성을 고려해야 할 필요는 없습니다. 그런 다음 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

내가 찾은 문제 중 하나는 두 객체가 서로에 대한 참조를 포함하는 곳입니다 (예 : 모든 자식을 얻는 부모의 편리한 메소드와 부모 / 자식 관계).
이러한 종류의 것들은 예를 들어 Hibernate 매핑을 할 때 상당히 일반적입니다.

hashCode 또는 equals 테스트에 관계의 양쪽 끝을 포함하면 재귀 루프로 들어가서 StackOverflowException으로 끝날 수 있습니다.
가장 간단한 해결책은 메소드에 getChildren 콜렉션을 포함시키지 않는 것입니다.


5
여기 기본 이론을 구별하는 생각 속성 , 집계associatinos 객체의. asssociations는 참여 안 equals(). 미친 과학자가 나에게 사본을 만들면 우리는 동등 할 것입니다. 그러나 우리는 같은 아버지가 없을 것입니다.
Raedwald
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.