본문 바로가기

programming/java

Item 11. equals 재정의할 때 hashCode도 정의하라

[Why]

hashCode를 재정의하지 않으면 해시를 이용한 컬랙션을 사용할 때 해시의 빠른 속도를 활용하지 못한다.

[When]

hashCode를 재정의할 때

[How]

hashCode 일반 규약

  • equals 비교에 사용되는 정보가 변경되지 않았다면, hashCode 도 변하면 안 된다.(애플리케이션을 다시 실행한다면 이 값이 달라져도 상관 없음)
  • equals가 두 객체가 같다고 판단했다면, 두 객체의 hashCode는 똑같은 값을 반환한다.→ 논리적으로 같은 객체는 같은 해시코드를 반환해야 한다.
  • equals가 두 객체를 다르다고 판단했더라도, hashCode는 꼭 다를 필요는 없다.하지만, 다른 객체에 대해서는 다른 값을 반환해야 해시테이블의 성능이 좋아진다.

hashCode를 작성하는 요령

  1. int 변수인 result를 선언한 후 값을 c로 초기화한다.
    • 이 때, c는 해당 객체의 첫번째 핵심 필드를 단계 2.1 방식으로 계산한 해시코드이다.
    • 여기서 핵심 필드는 equals 비교에 사용되는 필드를 말한다.
  2. 해당 객체의 나머지 핵심 필드인 f 각각에 대해 다음 작업을 수행한다.
    1. 해당 필드의 해시코드 c 를 계산한다.
      • 기본 타입 필드라면, Type.hashCode(f)를 수행한다. 여기서 Type은 해당 기본타입의 박싱 클래스다.
      • 참조 타입 필드면서, 이 클래스의 equals 메소드가 이 필드의 equals를 재귀적으로 호출하여 비교한다면, 이 필드의 hashCode를 재귀적으로 호출한다.
      • 필드가 배열이라면, 핵심 원소 각각을 별도 필드처럼 다룬다.모든 원소가 핵심 원소라면 Arrays.hashCode를 사용한다.
    2. 단계 2.1에서 계산한 해시코드 c로 result를 갱신한다.
      • result = 31 * result + c;
  3. result를 반환한다.
//예시
        @Override
    public int hashCode() {
        int result = Integer.hashCode(areaCode);
        result = 31 * result + Integer.hashCode(prefix);
        result = 31 * result + Integer.hashCode(lineNum);
        return result;
    }

hashCode 캐싱과 지연 초기화

  • 클래스가 불변이고 해시코드를 계산하는 비용이 크다면, 캐싱을 고려한다.
  • 해시의 키로 사용되지 않는 경우라면 hashCode가 처음 불릴 때 계산하는 지연 초기화
    • 해시코드 필드를 지연 초기화 하려면 그 클래스가 thread-safe가 되도록 동기화에 신경 쓰는 것이 좋다.