[Effective Java] 아이템11 - equals를 재정의했다면 hashCode도 재정의하라

equals 메소드와 hashCode 메소드는 친구!

equals 메소드는 물리적으로 다른 객체가 논리적으로는 동일하다고 판단할 수 있다.

그러나 hashCode 메서드는 이 둘이 전혀 다르다고 판단한다.

화면 캡처 2021-02-02 231928

PhoneNumber 객체에 hashCode가 없어서 아예 map에 put 자체가 안된다.

Error: No enclosing instance of type HashCode is accessible. Must qualify the allocation with an enclosing instance of type HashCode (e.g. x.new A() where x is an instance of HashCode).

위의 코드가 실행되게 하려면 hashCode를 작성해서 동치임을 알려줘야 한다.

hashCode 작성법

1. hashCode 계산

  1. 객체의 첫번째 핵심필드를 hash 해서 담아둔다.
  2. 나머지 객체 필드의 해시코드를 계산한다.
  3. result = 31 * result + c 로 해시코드를 반환한다.
    • 31을 곱하는 이유는 홀수이면서 소수이기 때문이다.
  • 기본 타입필드 : Type.hashCode(f) 예) Integer.hashCode
  • 참조 타입 필드 : 이 필드의 hashCode를 재귀적으로 호출
  • 필드 배열 : Arrays.hashCode

위의 해쉬코드를 계산하는 방법은 굳이 외울 필요 없이 그냥 알고만 있으면 될 것 같다.

@Override public int hashCode() {
    int result = Short.hashCode(areaCode); // 핵심필드의 hashCode로 초기화
    result = 31 * result + Short.hashCode(prefix);
    result = 31 * result + Short.hashCode(lineNum);
    return result;
}

2. Object.hash 를 이용

간단해보이지만, 성능이 조금 느리다.

@Override public int hashCode() {
    return Objects.hash(lineNum, prefix, areaCode);
}

3. 해시코드 캐싱

해시코드를 계산하는 비용이 크다면, 매번 새로 계산하기 보다는 캐싱하는 방식을 고려해야 한다.

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;
}

롬복은?

저번 시간처럼 롬복은 어떻게 구현하는지 볼까?

롬복의 @EqualsAndHashCode 어노테이션을 붙이면 hashCode를 생성해주는데, 아래와 같이 생성해준다고 문서에 나와 있다.

@Override public int hashCode() {
  final int PRIME = 59;
  int result = 1;
  result = (result*PRIME) + super.hashCode();
  result = (result*PRIME) + this.width;
  result = (result*PRIME) + this.height;
  return result;
}

오호라 여기서는 소수가 31이 아니라 59다. 조금 더 큰 수를 곱하는게 안전하나보다.

NEW in Lombok 1.18.16: The result of the generated hashCode() can be cached by setting cacheStrategy to a value other than CacheStrategy.NEVER.

문서에 따르면 캐싱 기능도 제공을 해준다.

@EqualsAndHashCode(cacheStrategy = CacheStrategy.LAZY)

Be Careful! Do not use this if objects of the annotated class can be modified in any way that would lead to the result of hashCode() changing.
수정되는 객체에는 해당 캐시 전략을 쓰면 안된다.

댓글남기기