equals
를 재정의하려거든 hashCode
도 재정의하라equals
를 재정의한 클래스는 hashCode
도 재정의 해야 한다.
그렇지 않으면 인스턴스를 HashMap
이나 HashSet
같은 컬렉션의 원소로 사용할 때 문제가 발생한다.
hashCode
일반 규약equals
비교에 사용되는 정보가 변경되지 않았다면, hashCode
도 변하면 안 된다.equals
가 두 객체가 같다고 판단했다면, 두 객체의 hashCode
는 똑같은 값을 반환한다.equals
가 두 객체를 다르다고 판단했더라도, hashCode
는 꼭 다를 필요는 없다.
equals
가 물리적으로 다른 두 객체를 논리적으로 같다고 할 때, hashCode
는 서로 다른 값을 반환한다.
Map<PhoneNumber, String> map = new HashMap<>();
map.put(new PhoneNumber(010,1234,5678), new Person("리치"));
이 코드에 map.get(new PhoneNumber(010,1234,5678))
를 실행하면 "리치"
가 아닌 null
을 반환한다.
PhoneNumber
클래스는 hashCode
를 재정의하지 않았기 때문에, 논리적 동치인 두 객체가 서로 다른 해시코드를 반환하여 get
메서드는 엉뚱한 해시 버킷에 가서 객체를 찾으려 한 것이다.
HashMap
은 해시코드가 서로 다른 엔트리끼리는 동치성 비교를 시도조차 않도록 최적화 되어있다.
@Override
public int hashCode() {
return 42;
}
안 된다. 모든 객체에게 똑같은 값만 내어주므로 모든 객체가 해시테이블의 버킷 하나에 담겨 마치 연결리스트처럼 동작한다. 그 결과 평균 수행 시간이 O(1)인 해시테이블이 O(n)으로 느려져서, 도저히 쓸 수 없게 된다.
hashCode
를 작성하는 요령result
를 선언한 후 값을 c로 초기화한다.equals
비교에 사용되는 필드를 말한다.Type.hashCode(f)
를 수행한다. 여기서 Type은 해당 기본타입의 박싱 클래스다.equals
메소드가 이 필드의 equals
를 재귀적으로 호출하여 비교한다면, 이 필드의 hashCode
를 재귀적으로 호출한다.Arrays.hashCode
를 사용한다.result
를 갱신한다.result
= 31 * result
+ c;result
를 반환한다.equals
비교에 사용되는 필드에 대해서만 해시코드를 계산한다.hashCode
메서드@Override
public int hashCode() {
int result = Integer.hashCode(areaCode);
result = 31 * result + Integer.hashCode(prefix);
result = 31 * result + Integer.hashCode(lineNum);
return result;
}
PhoneNumber
인스턴스의 핵심 필드 3개를 사용해 간단한 계산을 수행하고, 이 과정에 비결정적 요소는 없다.
→ 동치인 PhoneNumber
인스턴스는 서로 같은 해시코드를 가질 것이 확실하다.
Objects
클래스의 hashCode
메서드 - 성능이 살짝 아쉽다.@Override
public int hashCode() {
return Objects.hash(lineNum,prefix,areaCode);
}
입력 인수를 담기 위한 배열이 만들어지고, 입력 중 기본 타입이 있다면 박싱과 언박싱도 거친다. 속도가 느리다.
hashCode
의 캐싱과 지연 초기화hashCode
가 처음 불릴 때 계산한느 지연 초기화하면 좋다.hashCode
메서드 - 스레드 안전성까지 고려해야 한다.private int hashCode;
@Override
public int hashCode() {
int result = hashCode; // 초기값 0을 가진다.
if(result == 0) {
int result = Integer.hashCode(areaCode);
result = 31 * result + Integer.hashCode(areaCode);
result = 31 * result + Integer.hashCode(areaCode);
hashCode = result;
}
return result;
}
동시에 여러 쓰레드가 hashCode를 호출하면 여러 쓰레드가 동시에 계산하여 우리의 처음 의도와는 다르게 여러번 계산하는 상황이 발생할 수 있다.
그래서 지연 초기화를 하려면 동기화를 신경써주는 것이 좋다.
equals
를 재정의할 때는 hashCode
도 반드시 재정의해야 한다. 그렇지 않으면 프로그램이 제대로 동작하지 않을 것이다.
재정의한 hashCode
는 Object
의 API 문서에 기술된 일반 규약을 따라야 하며, 서로 다른 인스턴스라면 되도록 해시코드도 서로 다르게 구현해야 한다. AutoValue 프레임워클르 사용하면 멋진 equals
와 hashCode
를 자동으로 만들어준다.