equals와 hashCode는 왜 함께 재정의해야 할까요

equals와 hashCode는 객체의 동등성 비교와 해시값 생성을 위해 사용됩니다. 둘 중 하나만 재정의하면 HashSet, HashMap 같은 해시 기반 자료구조에서 예상과 다른 결과가 나올 수 있습니다.

equals와 hashCode의 계약

Java에서 equals()가 true인 두 객체는 반드시 같은 hashCode() 값을 가져야 합니다. 반대로 hashCode()가 같다고 해서 반드시 equals()가 true일 필요는 없습니다. 해시 충돌이 가능하기 때문입니다.

equals() true  -> hashCode() 같아야 함
hashCode() 같음 -> equals() true일 필요는 없음

equals만 재정의하면 생기는 문제

해시 기반 자료구조는 먼저 hashCode()로 버킷을 찾고, 같은 버킷 안에서 equals()로 동등성을 비교합니다.

class Subscribe {
    private final String email;
    private final String category;

    Subscribe(String email, String category) {
        this.email = email;
        this.category = category;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Subscribe that)) return false;
        return Objects.equals(email, that.email)
                && Objects.equals(category, that.category);
    }
}

이 클래스는 equals()만 재정의하고 hashCode()를 재정의하지 않았습니다. 그러면 내용이 같은 객체라도 Object의 기본 hashCode()를 사용해 서로 다른 해시값을 가질 수 있습니다.

Set<Subscribe> set = new HashSet<>();
set.add(new Subscribe("team.maeilmail@gmail.com", "backend"));
set.add(new Subscribe("team.maeilmail@gmail.com", "backend"));

System.out.println(set.size()); // 2가 될 수 있음

올바른 구현

equals() 비교에 사용한 필드를 hashCode()에도 함께 사용해야 합니다.

@Override
public int hashCode() {
    return Objects.hash(email, category);
}

정리

equals()와 hashCode()는 해시 기반 자료구조에서 함께 동작합니다. 논리적으로 같은 객체가 같은 버킷에서 비교될 수 있도록, equals()를 재정의할 때는 반드시 hashCode()도 함께 재정의해야 합니다.

CSLOG에서 보기