사용자 도구

사이트 도구


java:equals_hashcode

Java equals & hashCode

자주하는 equals & hashCode 실수 및 올바른 구현

잘못된 ''equals'' 메소드 시그너쳐

public boolean equals(ClassName other) {
  // 잘못된 equals!!
}
 
// 다음이 올바르다. 무조건 다음과 같이한다.
public boolean equals(Object other) {
...
}

equals가 변경되면 hashCode도 변경해야한다

  • equals가 사용하는 필드와 hashCode가 사용하는 필드를 동일하게 맞춰야 한다.
  • 그렇지 않으면 HashSet.contains 등이 올바르게 작동하지 않게 된다. HashSet은 hashCode 기반으로 bucket을 결정한다.
  • hashCode 규약
    • 두 개의 객체가 equals(Object) 호출시 true라면 두 객체의 hashCode()는 동일한 int 를 리턴해야 한다.
    • hashCodeequals에서 사용한 필드들만을 사용해야한다.

Mutable(변경가능한) 필드에 대한 equals/hashCode 구현은 하면 안 된다

  • 변경가능한 필드에 대해 equals, hashCode를 구현한 상태에서 해당 필드 값을 변경하면 동일 객체라도 hashCode가 바뀌게 된다.
  • 이 상태로 필드 변경전에 HashSet에 값을 넣었다면, 필드 값을 변경한 후에는 해당 객체를 HashSet에서 찾을 수 없게 된다. hashCode로 bucket을 결정하는데 hashCode가 바뀌었기 때문이다.

동치(equivalence)를 위반해서는 안된다

equalsnull이 아닌 객체간에는 항상 동치 관계를 유지하게 구현해야만 한다.

  • 반사성(reflexive) : null이 아닌 x에 대해 x.equals(x)는 항상 true여야 한다.
  • 대칭성(symmetric) : null이 아닌 x, y에 대해, 오직 y.equals(x)true일 때만 x.equals(y)true여야 한다. 대칭성 위반 예
  • 이행성(transitive) : null이 아닌 x,y,z에 대해, x.equals(y)true이고 y.equals(z)true이면 x.equals(z)true여야 한다. 이행성 위반 예
  • 일관성(consistent) : null이 아닌 x, y에 대해, 객체의 동등성 비교에 사용된 정보에 변경이 없다면 x.equals(y)를 여러번 호출해도 일관성있게 true를 반환하거나 일관성있게 false를 반환해야 한다.
  • null이 아닌 값 x 에 대해 x.equals(null)은 항상 false를 반환해야 한다.

상속 관계에서는 위 동치성을 쉽게 위반하게 된다. canEqual 메소드를 통해 이를 지켜내야 한다.

동치 문제의 최종 해결책 canEqual

equalshashCode를 override 할 때마다 can equal메소드도 함께 구현하고 오버라이드 해주면 된다. 이를 통해 현재 클래스의 상위클래스와 동등하게 평가되는 것을 막을 수 있다.

// Point 라는 클래스가 존재할 때
 
public boolean canEqual(Object other) {
    return (other instanceof Point);
}
 
@Override 
public boolean equals(Object other) {
    boolean result = false;
    if (other instanceof Point) {
        Point that = (Point) other;
        // that.canEqual(this) 가 핵심이다. 이를 거꾸로 this.canEqual(that) 으로 사용하면 안된다.
        result = (that.canEqual(this) && this.getX() == that.getX() && this.getY() == that.getY());
    }
    return result;
}
 
// 오버라이드 되는 ColoredPoint 클래스에서는
@Override
public boolean equals(Object other) {
    boolean result = false;
    if (other instanceof ColoredPoint) {
        ColoredPoint that = (ColoredPoint) other;
        result = (that.canEqual(this) && this.color.equals(that.color) && super.equals(that));
    }
    return result;
}
 
// that.canEqual(this) 호출을 통해서 상위클래스(Point)는 결코 ColoredPoint와 같을 수 없게 보장됨.
@Override 
public boolean canEqual(Object other) {
    return (other instanceof ColoredPoint);
}
  • 프로그래머는 상위클래스에 canEqual이 구현되어 있는 경우 하위 클래스에서 canEqual을 구현하거나 하지 않음으로써 상위클래스와의 동등성 비교를 허용하거나 안할 수 있다.

다른 타입간의 equals 탐지

java/equals_hashcode.txt · 마지막으로 수정됨: 2017/07/04 01:06 저자 kwon37xi