617

무시할 때 고려해야 할 문제점 / 함정equalshashCode?

11 답변


1383

이론 (언어 변호사와 수학적 경향) :

equals()(javadoc)는 동등한 관계를 정의해야합니다 (반사적 인,대칭의, 및전 이적). 또한, 그것은 있어야합니다일관된(객체가 수정되지 않으면 동일한 값을 반환해야합니다). 더욱이,o.equals(null)항상 false를 돌려 줄 필요가 있습니다.

hashCode()(javadoc)도 있어야합니다.일관된(오브젝트가equals()같은 값을 반환해야합니다).

그만큼관계두 가지 방법 사이의 차이점은 다음과 같습니다.

할때는 언제나a.equals(b), 그 다음에a.hashCode()~와 같아야합니다.b.hashCode().

실제로:

하나를 재정의하는 경우 다른 하나를 재정의해야합니다.

계산에 사용하는 것과 동일한 필드 집합을 사용하십시오.equals()계산하기hashCode().

우수한 도우미 클래스 사용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, 또는약한지도 맵개체가 컬렉션에있는 동안 컬렉션에 넣은 키 개체의 hashCode ()가 변경되지 않도록하십시오. 이것을 보장하는 방탄 방법은 키를 변경하지 못하게하는 것입니다.다른 이점도 있습니다.


  • appendSuper ()에 대한 추가 포인트 : 수퍼 클래스의 동등한 동작을 상속 받기를 원하면 hashCode () 및 equals ()에서 사용해야합니다. 예를 들어 Object에서 직접 파생하면 모든 객체가 기본적으로 별개이므로 별다른 의미가 없습니다. - Antti Kissaniemi
  • Eclipse를 통해 두 가지 방법, 즉 Source > hashCode () 및 equals ()를 생성합니다. - Rok Strniša
  • Netbeans에서도 마찬가지입니다.developmentality.wordpress.com/2010/08/24/… - seinecle
  • @Darthenius 이클립스에서 생성 된 equals는 getClass ()를 사용하여 어떤 경우에는 문제를 일으킬 수 있습니다 (Effective Java item 8 참조). - AndroidGecko
  • 사실을 고려할 때 첫 번째 null 확인은 필요하지 않습니다.instanceof첫 번째 피연산자가 null 인 경우 false를 반환합니다 (다시 Effective Java). - izaban

282

Hibernate와 같은 객체 - 관계 매퍼 (Object-Relationship Mapper, ORM)를 사용하여 지속되는 클래스를 다루는 경우,이 클래스가 부당하게 복잡하다고 생각하지 않는다면주의해야 할 몇 가지 문제가 있습니다.

게으른로드 된 객체는 하위 클래스입니다.

객체가 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은 일반적으로 게터를 사용하여로드가 지연된 객체를 강제로로드합니다. 이것은person.name될거야null만약personlazy가로드 되어도person.getName()로딩하고 "John Doe"를 반환합니다. 내 경험으로 볼 때,hashCode()equals().

ORM을 다루는 경우 항상 getter를 사용하고 필드 참조는 절대로 사용하지 마십시오.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()(편집증 만) forequals(). 만약에 "충돌"의 위험이 있다면 괜찮습니다.hashCode(),하지만 결코 괜찮아요.equals().

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


  • @ Johannes Brodwall : 이해할 수 없습니다.Saving an object will change it's state!hashCode돌아와야한다.int, 그래서 어떻게 사용 하시겠습니까?getName()? 당신에게 당신의 모범을 보여줄 수 있습니까?hashCode - jimmybondy
  • @jimmybondy : getName은 사용할 수있는 hashCode가있는 String 객체를 반환합니다. - mateusz.fiolka

82

그 사실에 대한 설명obj.getClass() != getClass().

이 진술은equals()비우호적 인 유산. JLS (Java 언어 사양)에서는 ifA.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))결과는 사실대로 밝혀야합니다.

이것은 모두 매우 좋게 보이지만 두 클래스를 모두 사용하려고하면 어떻게되는지보십시오.

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() 너무.


  • obj.getClass ()! = this.getClass () & & obj.getClass (). isInstance ()와 같은 방식으로 equals를 대칭으로 만들 수 있습니다 (수퍼 클래스 객체와 하위 클래스 객체를 비교하는 경우 항상 하위 클래스의 equals를 사용하십시오) (this))) return obj.equals (this); - pihentagy
  • @pihentagy - 구현 클래스가 equals 메소드를 대체하지 않으면 스택 오버 플로우가 발생합니다. 재미 없어. - Ran Biron
  • 스택 오버 플로우가 발생하지 않습니다. equals 메서드가 재정의되지 않으면 동일한 코드를 다시 호출하지만 재귀 조건은 항상 false입니다! - Jacob Raihle
  • @pihentagy : 두 개의 서로 다른 파생 클래스가있는 경우 어떻게 동작합니까? 만약ThingWithOptionSetAa와 같을 수있다.Thing모든 추가 옵션에는 기본값이 있고,ThingWithOptionSetB, 다음 경우에 가능해야합니다.ThingWithOptionSetA비교할만한ThingWithOptionSetB두 객체의 모든 비 기본 속성이 기본값과 일치하는 경우에만 해당 객체를 테스트하는 방법을 볼 수 없습니다. - supercat
  • 이것이이 과도기를 깨뜨리는 문제입니다. 추가하는 경우B b2 = new B(1,99), 그 다음에b.equals(a) == truea.equals(b2) == true그러나b.equals(b2) == false. - nickgrim

43

상속 친화적 인 구현을 위해서는 Tal Cohen의 솔루션을 확인하십시오.어떻게 equals () 메소드를 올바르게 구현합니까?

개요:

그의 책에서효과적인 자바 프로그래밍 언어 가이드(Addison-Wesley, 2001) 조슈아 블로흐 (Joshua Bloch)는 "인스턴스화 가능한 클래스를 확장하고 equals 계약을 유지하면서 aspect를 추가하는 방법은 없습니다." 탈 (Tal)은 의견이 맞지 않는다.

그의 해결책은 다른 비대칭적인 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);
    }
}

equals ()는 상속 계층에서 작동해야합니다.리 스키프 대치 원리만족해야한다.


  • 같은 원리로 두 솔루션을 모두 사용할 수 있지만 canEqual을 사용하면 같은 필드를 두 번 비교하지 않아도됩니다 (위의 p.x == this.x는 양방향으로 테스트됩니다).artima.com/lejava/articles/equality.html - Blaisorblade
  • Point.equals ()는 Point로 캐스팅되어야합니다 (객체에서 blindlyEquals ()를 호출하려고합니다). 그것이 의도적이었을 수도 있지만 (방법을 강조하려는 시도에서). - Kevin
  • 어쨌든 나는 이것이 좋은 생각이라고 생각하지 않는다. 그것은 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파생 된 클래스가 equals를 수정해야하는 경우에만 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());
    }


  • java.util.Objects.hash () 및 java.util.Objects.equals ()는 Java 7 (2011 년에 릴리스 됨)의 일부이므로 Guava가 필요하지 않습니다. - herman
  • 물론 오라클이 자바 6에 대한 공개 업데이트를 더 이상 제공하지 않기 때문에 (2013 년 2 월 이후) 오라클을 피하는 것이 좋습니다. - herman
  • 너의this...에서this.getDate()아무것도 의미하지 않는다 (혼란을 제외하고) - Steve Kuo
  • 귀하의 " not instanceof " expression에는 추가 대괄호가 필요합니다.if (!(otherObject instanceof DateAndPattern)) {. hernan 및 Steve Kuo와 동의하지만 (개인 취향의 문제 임에도 불구하고) 그럼에도 불구하고 +1합니다. - Amos M. Carpenter

26

슈퍼 클래스에는 java.lang.Object라는 두 가지 메소드가있다. 그것들을 커스텀 객체에 오버라이드 (override)해야합니다.

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

18

회원 평등을 점검하기 전에 평등을 점검하는 두 가지 방법이 있습니다. 둘 다 올바른 상황에서 유용하다고 생각합니다.

  1. 사용instanceof운영자.
  2. 용도this.getClass().equals(that.getClass()).

나는 #1을 사용한다.final구현과 같거나 equals 알고리즘을 규정하는 인터페이스를 구현할 때 (예 :java.util컬렉션 인터페이스 - 함께 확인하는 올바른 방법(obj instanceof Set)또는 당신이 구현하고있는 인터페이스). 대칭 속성을 깨뜨리기 때문에 equals를 재정의 할 수있는 경우 일반적으로 나쁜 선택입니다.

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

당신의 수업이 또한Comparable,equalscompareTo메소드도 일관성이 있어야합니다. 다음은 equals 메소드의 템플릿입니다.Comparable수업:

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. getClass ()도 instanceof도 만병 통치약이 아닙니다.이 두 가지 방법에 대한 좋은 설명입니다. equals ()를 사용하는 대신 this.getClass () == that.getClass ()를 수행하지 않을 이유가 있다고 생각하지 마십시오. - Paul Cantrell
  • 이 문제에는 한 가지 문제가 있습니다. 애스 팩트를 추가하지 않고 equals 메소드를 오버라이드 (override)하지 않는 익명 클래스는, getClass 체크가 동일하지 않아도 실패합니다. - Steiny
  • @Steiny 다른 유형의 객체가 동일해야한다는 것은 분명하지 않습니다. 공용 익명 클래스와 인터페이스의 구현이 다르기 때문에 생각합니다. 당신은 당신의 전제를 뒷받침 할 모범을 줄 수 있습니까? - erickson
  • MyClass a = 새로운 MyClass (123); MyClass b = 새로운 MyClass (123) {// 일부 메소드를 오버라이드합니다.}; //.e.equals (b)는 this.getClass (). equals (that.getClass ())를 사용할 때 false입니다. - Steiny
  • @ 멋지다. 대부분의 경우에 그렇듯이, 특히 메서드가 추가되는 대신 재정의되는 경우가 그렇습니다. 위의 예를 고려하십시오. 그렇지 않은 경우final, 그리고compareTo()메서드가 오버라이드되어 정렬 순서가 바뀌 었으며 하위 클래스와 수퍼 클래스의 인스턴스를 동일하게 간주해서는 안됩니다. 이러한 객체들이 트리에서 함께 사용될 때, "동일하다" ~에 따르면instanceof구현을 찾지 못할 수도 있습니다. - erickson

15

equals에 대해서는 다음을 살펴보십시오.평등의 비밀으로안젤리카 랑거. 나는 그것을 아주 좋아한다. 그녀는 또한 대단한 FAQ입니다.Java의 제네릭. 다른 기사보기이리( "Core Java"로 스크롤) Part 2 및 "혼합 유형 비교"에서 계속 진행됩니다. 그 (것)들을 읽는 재미를보십시오!


11

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

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

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

기본 구현은 객체 클래스의 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 컬렉션을 포함하지 않는 것입니다.


  • 여기에있는 기본 이론은속성들,집합체동료들개체의 그만큼엉덩이참여해서는 안된다.equals(). 미친 과학자가 저를 복제 한 것이라면 우리는 동등 할 것입니다. 그러나 우리에게는 같은 아버지가 없을 것입니다. - Raedwald

연결된 질문


관련된 질문

최근 질문