Search

Item 11. equals를 재정의하려거든 hashCode도 재정의하라

생성일
2023/07/27 10:36
챕터
3장 - 모든 객체의 공통 메서드

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 사용자에게 알리지 말자. 그래야 클라이언트가 해당 값에 의지하지 않고, 추후 계산 방식을 바꿀 때 문제가 없다.