hashCode 재정의하기
•
equals를 재정의한 클래스라면 항상 hashCode도 재정의 해야한다.
•
Object 명세에 있는 hashCode 규약을 살펴보면, 두 객체가 같다고 판단하면 두 객체의 hashCode 메서드는 똑같은 값은 반환해야한다는 항목이 있다. 다시말해, 논리적으로 같은 객체는 같은 해시코드를 반환해야 한다.
Map<PhoneNumber, String> m = new HashMap<>();
m.put(new PhoneNumber(707, 867, 5309), "제니");
Java
복사
•
이와 같은 코드에서 m.get(new PhoneNumber(707, 867, 5309), "제니")을 호출하게되면 “제니”가 나오길 기대하지만, 실제로는 null이 반환된다.
•
그 이유는 논리적 동치인 두 객체가 서로 다른 해시코드 값을 반환하여, HashMap에서 엉뚱한 버킷에 가서 찾으려 시도했기 때문이다. 우연히 같은 버킷에 담겼다고 하더라도 HashMap은 해시코드가 다른 두 엔트리끼리는 동치성 비교를 시도조차 하지 않도록 최적화 되어있어 null이 반환된다.
•
hashCode 메서드를 재정의하여 이런 문제가 발생하지 않도록 해야한다.
좋은 hashCode의 해시 함수 만들기
•
좋은 hashCode를 작성하는 방법은 다음과 같다.
1.
c(초기값)으로 초기화된 int 변수 result를 선언한다.
2.
객체의 나머지 핵심 필드에 대해서 중요한 필드 순으로 result = 31 * result + hashCode를 수행한다. 여기서 hashCode는 해당 필드의 Type.hashCode() 값일 수도 있고, 해당 필드의 값일 수도 있다.
3.
핵심 필드를 전부 반영하여 계산하기까지 위의 2번 과정을 반복한다.
•
31을 곱하는 이유는 홀수이면서 소수이고 적당히 큰 수로, 아나그램과 같은 순서만 다른 경우 해시코드가 같게 나오는 경우를 방지하기 위함이다. 추가적으로 (1 << 5) - 1로 연산을 최적화할 수도 있다.
private int hashCode;
@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;
}
Java
복사
•
중요한 것은 성능을 높이겠다고 해시코드 계산에 핵심 필드를 생략해서는 안된다. 해시 품질이 나빠져 해시테이블의 성능이 심각하게 떨어져, 오히려 성능이 나빠질 수 있다.
•
추가적으로 hasCode 메서드가 반환하는 해시코드의 생성 규칙을 API 사용자에게 알리지 말자. 그래야 클라이언트가 해당 값에 의지하지 않고, 추후 계산 방식을 바꿀 때 문제가 없다.