Language/Java

Java - Equals()랑 HashCode()를 반드시 같이 재정의해야 하는 이유!

JaeHoney 2022. 4. 24. 11:10

equals()

Equals와 HashCode를 재정의하는 이유와 둘 사이에 관계에 대해 알아보자.

 

Point라는 클래스가 있다고 가정한다.

class Point {
    int x;
    int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

 

그리고 아래의 테스트를 돌려보면 테스트가 깨진다. equals가 기본적으로 java core 라이브러리에 있는 equals로 동작하는데 내부적으로 참조(주소)를 기준으로 비교하기 때문이다.

@Test
void test() {
    Point point1 = new Point(1, 2);
    Point point2 = new Point(1, 2);
    Assertions.assertThat(point1).isEqualTo(point2); // Tests failed
}

그래서 equals()를 재정의해서 참조가 아니라 값을 기준으로 정의할 수 있다.

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Point point = (Point) o;
    return x == point.x && y == point.y;
}

이제 같은 테스트를 돌려보면 성공한다.

assertEquals(point1, point2); // Tests passed

 

hashcode()

많은 분들이 잘 모르시는 hashcode()이다. 아까 equals() 메서드를 재정의해서 객체를 값을 기반으로 비교하도록 수정했다.

 

그렇다면 아래의 테스트는 성공할까?

@Test
void test() {
    Set<Point> set = new HashSet<>();
    set.add(new Point(1, 2));
    set.add(new Point(1, 2));
    Assertions.assertThat(set.size()).isEqualTo(1); // Tests failed
}

정답은 실패한다!

왜냐하면 HashSet, HashMap 등에 삽입할 때 비교하는 hashCode의 기준이 아직 참조이기 때문이다!

 

그래서 hashCode()도 반드시 재정의해야 한다. Point 클래스에 아래의 메서드를 추가하자.

@Override
public int hashCode() {
    return Objects.hash(x, y);
}

이제 테스트를 돌려보면?! Tests passed 성공한다!

 

그런데! 여기서 equals method는 다시 제거해보자. 해당 테스트가 통과할까?

public class Point {
    int x;
    int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

그리고 다시 아까 돌렸던 HashSet 테스트를 돌려보면 ? 테스트가 깨진다! 뭐야?!

@Test
void test() {
    Set<Point> set = new HashSet<>();
    set.add(new Point(1, 2));
    set.add(new Point(1, 2));
    Assertions.assertThat(set.size()).isEqualTo(1); // Tested Failed
}

 

equals()와 hashCode()의 관계

Hashed Collections는 Key를 Hash해서 유일 여부를 판단하는 것 아닌가..?

 

해시 자료구조에서는 hashCode로 먼저 비교를 하고, 결과가 같다면 equals로 추가로 비교한다!

 

equals()에 의해 true가 나오는 두 객체의 hashcode()는 같아야 한다. 하지만 hashcode()가 같다고 반드시 equals()의 결과도 같다는 것은 아니다!

 

"이게 무슨 소린지 예를 들어 보자"

 

문자열 "aaa"의 Hash code를 확인했더니 "11314230" 이라는 숫자가 나왔다.

새로운 "aaa" 문자열 객체를 만들어서 Hash code를 확인해도 "11314230"이다.

 

그렇다고 해서 "11314230"이라는 해시 코드를 문자열 "aaa"만 가질 수 있나?! 아니다!

 

다른 문자열도 해당 hashcode를 가질 수 있다!

 

그런 이유로 해시 자료구조에서는 hashcode로 비교를 한 후, 추가적으로 equals도 비교를 하고 같지 않기 때문에 해시가 충돌했다고 판단해서 새로운 인덱스에 데이터를 생성하게 된다. 그래서 hashcode만 재정의하면 해시 자료구조가 원활하게 동작하지 않는 것이다!