아이템 11 - equals를 재정의하려거든 hashCode도 재정의해라
JAVA ·equals를 재정의한 클래스 모두에서 hashCode를 재정의해야 한다. 그렇지 않으면 hashCode 일반 규약을 어기게 되어 해당 클래스의 인스턴스를 HashMap이나 HashSet 같은 컬렉션의 원소로 사용할 때 문제를 일으킬 것이다.
hashCode 규약
- equals 비교에 사용되는 정보가 변경되지 않았다면, 애플리케이션이 실행되는 동안 hashCode 메서드는 매번 같은 값을 반환해야 한다.
- 애플리케이션을 다시 실행한다면 이 값이 달라져도 상관없다.
- 두 객체에 대한 equals가 같다면, hashCode의 값도 같아야 한다.
- hashCode 재정의를 잘못했을 때 크게 문제가 되는 조항
- 즉, 논리적으로 같은 객체는 같은 해시코드를 반환해야 한다.
- 두 객체에 대한 equals가 다르더라도 hashCode의 값은 같을 수 있지만 해시 테이블 성능을 고려해 다른 값을 반환하는 것이 좋다.
- 해시 충돌을 피해주는 것으로 O(1)의 성능을 유지할 수 있도록 하는 것이 좋다.
- 자바 8에서 해시 충돌시 성능 개선을 위해 내부적으로 동일한 버켓에 일정 개수(8개) 이상의 엔트리가 추가되면, 연결리스트 대신 이진 트리(레드 블랙 트리)를 사용하도록 바뀜, O(N) -> O(logN)
-
HashMap Performance Improvements in Java 8 - DZone
</br>
</br>
hashCode 구현 방법
-
HashMap Performance Improvements in Java 8 - DZone
</br>
</br>
- 1) 핵심 필드 하나의 값의 해쉬값을 계산해서 result 값을 초기화한다.
- 2) 기본타입은 Type.hashCode
참조 타입은 해당 필드의 hashCode
배열은 모든 원소를 재귀적으로 위의 로직을 적용하거나, Arrays.hashCode
result = 31 * result + 해당 필드의 hashCode 계산값 - 3) 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; }
</br> </br>
hashCode 구현 대안
- 구글 구아바의 com.google.common.hash.Hashing
- 해시 충돌이 더욱 적은 방법
- Object 클래스의 hash 메서드
- 성능이 약간 아쉽지만, 충분히 사용할 만하다.
- @EqualsAndHashCode
- 캐싱을 사용해 불변 클래스의 해시 코드 계산 비용을 줄일 수 있다. </br> </br>
주의사항
- 지연 초기화 기법을 사용할 때 스레드 안전성을 신경써야 한다.
- 성능 때문에 핵심 필드를 해시코드 계산할 때 빼면 안된다.
- 해시코드 계산 규칙을 API에 노출하지 않는 것이 좋다. </br> </br>