[Effective Java 3/e] 아이템 11 - equals를 재정의하려거든 hashCode도 재정의하라


아이템 11 - equals를 재정의하려거든 hashCode도 재정의하라

  • equals를 재정의한 클래스 모두에서 hashCode도 재정의해야 한다.
  • 논리적으로 같은 객체는 같은 해시코드를 반환해야 한다.

최악의(하지만 적법한) hashCode 구현 - 사용 금지

@Override public int hashCode() { return 42; }
  • 모든 객체에 똑같은 값을 내어주므로 모든 객체가 해시 테이블의 버킷 하나에 담겨 연결 리스트처럼 동작
    • O(1) -> O(n) 으로 느려짐

좋은 hashCode 작성하는 방법

  • int 변수 result를 선언한 후 값 c로 초기화한다.
    • c는 해당 객체의 첫번째 핵심 필드를 해시코드로 계산 한 것
  • 해당 객체의 나머지 핵심 필드 f 가각에 대해 다음 작업을 수행한다.
    • 해당 필드의 해시코드 c를 계산한다.
    • 계산한 해시코드 c로 result를 갱신한다.
    • ex) result = 31 * result + c;
      • 곱할 숫자가 31인 이유
        • 홀수이면서 소수(prime)이기 때문
        • 소수를 곱하면 곱셈을 시프트 연산과 빨셈으로 대체해 최적화 할 수 있다.
        • 31 * i = (i « 5) - i
  • result를 반환한다.

전형적인 hashCode 메서드

@Override public int hashCode() {
  int result = Short.hashCode(areaCode);
  result = 31 * result + Short.hashCode(prefix);
  result = 31 * result + Short.hashCode(lineNum);
  return result;
}

한 줄짜리 hashCode 메서드 - 성능이 살짝 아쉽다

@Override public int hashCode() {
  return Objects.hash(lineNum, prefix, areaCode);
}
  • 속도가 느리다.
    • 입력 인수를 담기 위한 배열이 만들어지고, 입력 중 기본 타입이 있다면 박싱과 언박싱도 거쳐야 하기 때문
    • 성능에 민감하지 않을 때 사용 가능

해시코드를 지연 초기화하는 hashCode 메서드 - 스레드 안정성까지 고려해야 한다

private int hashCode; // 자동으로 0으로 초기화된다.

@Override public int hashCode() {
  int result = hashCode;
  if (result == 0) {
    result = Short.hashCode(areaCode);
    result = 31 * result + Short.hashCode(prefix);
    result = 31 * result + Short.hashCode(lineNum);
    hashCode = result;
  }
  return result;
}
  • 클래스가 불변이고 해시코드 계산 비용이 크다면 캐싱하는 방법을 고려해야 한다.