티스토리 뷰
hashCode 메서드를 재정의해야하는 이유
equals를 재정의한 클래스에서 hashcode 를 재정의하지 않으면, 일반 규약을 어기게 된다. 일반 규약을 어긴 클래스의 인스턴스는 HashMap 이나 HashSet 같은 hash 로직을 기반으로 하는 컬렉션의 원소로 사용할 때 문제를 일으키게 된다.
Object 명세 규약
- equals 비교에 사용되는 정보가 변경되지 않았다면, 애플리케이션이 실행되는 동안 그 객체의 hashCode 메서드는 몇 번을 호출해도 일관되게 항상 같은 값을 반환해야 한다. 단, 애플리케이션 재실행시에는 이 값이 달라져도 상관없다.
- equals(Object) 가 두 객체를 같다고 판단했다면, 두 객체의 hashCode 는 같은 값을 반환해야 한다.
- equals(Object) 가 두 객체를 다르다고 판단했더라도, 두 객체의 hashCode가 서로 다른 값을 반환할 필요는 없다. 단, 다른 객체에 대해서는 다른 값을 반환해야 해시테이블의 성능이 좋아진다
위 규약에서 두 번째 조항이 중요하다. 논리적으로 같은 객체에 대해서 같은 해시코드를 반환해 주어야 한다. 논리적으로 같은 객체를 물리적으로 다른 공간에 인스턴스화 하는경우 기본적인 hashCode 메서드는 서로 다른 값을 반환한다.
좋은 해시 함수라면 서로 다른 인스턴스에 다른 해시코드를 반환한다.
좋은 해시코드를 작성하기 위한 요령
- int 변수 result 를 선언한 후 값 c로 초기화한다. 이 때 c는 해당 객체의 첫 번째 핵심필드를 단계 2.1 방식으로 계산한 해시코드이다.
- 해당 객체의 나머지 핵심필드 f 각각에 대해 다음 작업을 수행한다
- 해당 필드의 해시코드 c를 계산한다
- 기본타입 필드라면 Type.hashCode(f)를 수행한다
- 참조타입 필드면서 이 클래스의 equals 메서드가 이 필드의 equals 를 재귀적으로 호출한다면, 이 필드의 hashCode를 재귀적으로 호출한다. 필드의 값이 null 이면 0을 사용한다.
- 필드가 배열이라면, 핵심 원소 각각을 별도 필드처럼 다룬다. 배열에 핵심 원소가 하나도 없다면 상수 0을 사용한다. 모든 원소가 핵심 원소라면 Arrays.hashCode를 사용한다.
- 단계 2.1 에서 계산한 해시코드 c로 result를 갱신한다.
ex) result = 31 * result + c;
- 해당 필드의 해시코드 c를 계산한다
- result를 반환한다.
파생 필드는 해시코드 계산에서 제외해도 된다. 또한 equals 비교에 사용되지 않은 필드는 반드시 제외해야 한다. 그렇지 않으면 Object 명세규약의 두 번째 조항을 어기게 될 수 있다.
2-2 의 곱셈 31 * result 는 클래스에 비슷한 필드가 여러 개일 때 해시효과를 크게 높여준다. 곱하는 숫자가 31인 이유는 31이 홀수이면서 소수이기 때문이다.
hashCode 메서드의 성능
Object 클래스는 임의의 개수만큼 객체를 받아 해시코드를 계산해주는 hash static 메서드를 제공한다. 속도는 더 느리기 때문에 성능에 민감하지 않은 상황에서만 사용하는 것이 좋다.
클래스가 불변이고 해시코드를 계산하는 비용이 크다면, 매번 새로 계산하기 보다는 캐싱하는 방식을 고려해야 한다. 인스턴스 생성시에 해시코드를 계산해 둘 수 있다. hashCode 가 처음 불릴 때 계산하는 지연 초기화 전략을 택할 수도 있다. 대신에 그 클래스를 스레드에 안전하게 만들도록 설계해 주어야 한다.
마지막으로 성능향상을 위해 해시코드 계산로직에서 핵심 필드를 생략해서는 안된다.
hashCode 는 비즈니스 로직이 아니다
hashCode가 반환하는 값의 생성 규칙을 API 사용자에게 자세히 공표할 필요는 없다. 클라이언트가 이 값에 의지해서는 안된다.
정리
hashCode 메서드는 equals 메서드 구현에 따라오는 개념으로, 또 요즘에는 롬복과 같은 라이브러리가 제공하는 기능을 더 잘 이해하기 위한 개념으로만 인지하는 것 같다. Object 규약에 따라 equals 메서드와 hashCode 메서드의 의미에 미묘한 차이가 있는 것이 흥미롭다.
'프로그래밍 > Effective Java' 카테고리의 다른 글
아이템 13. clone 재정의는 주의해서 진행하라 (1) | 2024.08.04 |
---|---|
아이템 12. toString을 항상 재정의하라 (1) | 2024.07.14 |
아이템 10. equals는 일반 규약을 지켜 재정의하라 (0) | 2024.07.07 |
아이템 9. try-finally 보다는 try-with-resources 를 사용하라 (0) | 2024.07.07 |
아이템 8. finalize와 cleaner 사용을 피하라 (0) | 2024.07.07 |
- Total
- Today
- Yesterday