짧은 코멘트와 함께하는 이펙티브 자바) #11 equals를 재정의하려거든 hashCode도 재정의하라
짧은 코멘트
- equals 관련 코멘트에서 다루었던 것처럼
@EqualsAndHashCode
를 사용할 수 있다. 단, 이때 컬렉션이나 순환참조 객체 등을@EqualsAndHashCode
대상 필드에 포함시키면 문제가 될 수 있다. - IntelliJ 에서 command + N(윈도우는 아마 control + N..?) 을 눌러, equals And hashCode를 자동으로 만들어줄 수 있다.
hashCode
equals를 재정의한 클래스 모두에서는 hashCode도 재정의 해야 한다 hashCode의 규약 중 일부는 이렇다 - equals 비교에 사용되는 정보가 변경되지 않았다면, 애플리케이션이 실행되는 동안 그 객체의 hashCode 메소드는 몇 번을 호출해도 일관되게 항상 같은 값을 반환해야 한다 - equals가 두 객체를 같다고 판단했다면, 두 객체의 hashCode는 똑같은 값을 반환해야 한다 (보통 hashCode를 재정의하면 이 항목이 문제가 된다) - equals가 두 객체를 다르다고 판단했더라도, 두 객체의 hashCode가 서로 다른 값을 반환할 필요는 없다. 단, 다른 객체에 대해서는 다른 값을 반환해야 해시테이블의 성능이 좋아 진다
// 적법하지만 최악의 hashCode 구현
@Override
public int hashCode() {
return 42;
}
좋은 hashCode를 작성하는 간단한 요령
- int 변수 result를 객체의 첫 번째 핵심 필드 f에 대한 hashCode로 초기화 한다 이때 #1 핵심 필드가 primitive type이면 Type.hashCode(f)를 계산하고, #2 reference type이면 해당 f.hashCode()를 호출한다. 만약 계산이 복잡해질 것 같으면 이 필드의 표준형을 만들어 그 표준형의 hashCode를 호출해도 된다. 값이 null이라면 상수(관습적으로 0)를 사용한다 #3 핵심 필드가 배열이라면 각 필드에 대해 #1 또는 #2를 반복한다. 만약 모든 필드가 핵심 필드라면 Array.hashCode를 사용할 수 있다
- 이후 나머지 핵심 필드에 대해서도 위에 언급된 방법대로 해시코드를 계산하여 result를 갱신한다
result = 31 * result + f.hashCode()
- result를 반환한다
이때 핵심 필드가 아닌 필드는 반드시 제외 해야 한다
예시
@Override
public int hashCode() {
int result = Short.hashCode(areaCode);
result = 31 * result + Short.hashCode(prefix);
result = 31 * result + Short.hashCode(lineNum);
return result;
}
이 방법이면 충분히 훌륭한 해시코드가 나오게 된다. 만약 해시 충돌이 더욱 적은 방법을 사용해야 한다면 구아바의 com.google.common.hash.Hashing
을 참고할 수 있다
Objects 클래스르의 Objects.hash를 사용할 수도 있다. 단 속도가 조금 느리니, 성능이 주요 고려 사항이 아닐 때 사용할만하다.
캐싱
클래스가 불변이고 해시코드를 계산하는 비용이 큰 경우 캐싱을 사용할 수 있다. 특히, 주로 key로 사용되는 객체라면 인스턴스가 만들어질 때 해시코드를 계산해두어야 한다 만약 인스턴스가 만들어질 때 해시코드를 계산하는 것이 아닌 hashCode가 처음 불릴 때 계산하는 지연 초기화 전략을 사용하려면 그 클래스를 Thread-safe 하게 만들어야 한다
팁
hashCode가 반환하는 값의 생성 규칙을 API 사용자에게 자세히 공표하지 않는 편이 좋다. 그래야 클라이언트가 이 값에 의지하지 않게 되고, 추후에 계산 방식을 바꿀 수 있다.