[이펙티브 자바] 아이템11

hyng·2022년 12월 10일
0

이펙티브 자바

목록 보기
11/13

equals를 재정의하려거든 hashCode도 재정의하라

equals와 hashCode를 함께 재정의하지 않으면 HashMap이나 HashSet 같은 컬렉션의 원소로 사용할 때 문제가 발생한다.



HashMap.containskey()의 내부 코드를 확인해 보면 두 객체의 hashcode가 같아야 하고 둘의 key 참조가 같거나, equals 반환값이 true일 경우 동일한 객체라고 판단한다.

관련 내용 참조

Obect 명세에 명시된 규약을 지키자

  • equals 비교에 사용되는 정보가 변경되지 않았다면, 애플리케이션이 실행되는 동안 그 객체의 hashCode 메서드는 몇 번을 호출해도 일관되게 항상 같은 값을 반환해야 한다.
  • equals(Object)가 두 객체를 같다고 판단했다면, 두 객체의 hashCode는 똑같은 값을 반환해야 한다.
  • equals(Object)가 두 객체를 다르다고 판단했더라도, 두 객체의 hashCode가 서로 다른 값을 반환할 필요는 없다. 단, 다른 객체에 대해서는 다른 값을 반환해야 해시테이블의 성능이 좋아진다.

equals(Object)가 두 객체를 같다고 판단했다면, 두 객체의 hashCode는 똑같은 값을 반환해야 한다.

이 규약을 지키지 못한다면 다음의 문제가 발생하게 된다.

    Point p1 = new Point(1, 2);
    Point p2 = new Point(1, 2);

    // p1과 p2는 논리적 동치
    Set<Point> set = new HashSet<>();
    set.add(p1);

    System.out.println(set.contains(p2)); // false

p1과 p2는 분명 논리적 동치이기 때문에 contains 호출 결과로 true가 나오길 기대했다. 하지만 결과는 false가 나온다.
그 이유는 hashCode 값이 다르기 때문이다. 즉, contains 호출 시 엉뚱한 해시 버킷에 가서 객체를 찾기 때문에 false를 반환하게 된다. 이것은 분명히 의도했던 바가 아니다.

그렇다면 hashCode 호출 시 항상 동일한 값을 반환하도록 한다면 어떨까?

  @Override
  public int hashCode() {
    return 12;
  }
    Point p1 = new Point(1, 2);
    Point p2 = new Point(1, 2);

    // p1과 p2는 논리적 동치
    Set<Point> set = new HashSet<>();
    set.add(p1);

    System.out.println(set.contains(p2)); // true

예상했던 대로 true가 나온다. 하지만 이 방법의 경우 모든 객체가 해시테이블의 버킷 하나에 연결 리스트로 이어져 저장되기 때문에 평균 수행 시간이 O(1)에서 O(n)으로 느려지게 된다.
좋은 해시 함수라면 서로 다른 인스턴스에 다른 해시 코드를 반환한다.

몇 가지 고려할 점

  • hashCode 작성 시 equals 비교에서 사용되지 않은 필드는 반드시 제외해야 한다. 그렇지 않으면 두 번째 규약을 어기게 될 수 있다.
  • 클래스가 불변이고 해시 코드를 계산하는 비용이 크다면, 캐싱 하는 방식을 고려하자.
profile
공부하고 알게 된 내용을 기록하는 블로그

0개의 댓글