hashCode의 규약을 지켜라

hashCode()는 수많은 컬렉션, 알고리즘에 사용되는 자료구조인 해시 테이블 구축 시 사용된다.

해시 테이블

컬렉션에 요소를 빠르게 추가하고 추출해야 한다고 친다. 이 때 사용할 수 있는 컬렉션은 Set, Map이 있다. 이들은 중복을 허용하지 않는다.

따라서 요소를 추가할 때 동일한 요소가 이미 들어 있는지 확인해야 한다. 배열 또는 링크드 리스트 기반으로 만들어진 컬렉션은 요소가 포함돼 있는지 확인하는 성능이 좋지 않다.

수백만 개의 텍스트가 포함된 배열에 특정 텍스트가 포함돼 있는지 확인해야 할 경우, 수백만 개의 텍스트를 선형으로 비교한다면 꽤 오랜 시간이 걸릴 것이다.

성능을 좋게 만드는 해결법이 해시 테이블이다. 해시 테이블은 각 요소에 숫자를 할당하는 함수가 필요하다. 이 함수를 해시 함수라고 부르며 같은 요소라면 항상 같은 숫자를 리턴한다. 해시 함수가 아래 특성을 갖고 있으면 좋다.

빠르다
충돌이 적다(다른 값이면 최대한 다른 숫자를 리턴한다)

해시 함수는 각 요소에 특정 숫자를 할당하고 이를 기반으로 요소를 다른 버킷(bucket)에 넣는다. 또한 해시 함수의 기본 조건(같은 요소면 항상 같은 숫자 리턴)에 의해 같은 요소는 항상 동일한 버킷에 넣게 된다. 버킷은 버킷 수와 같은 크기의 배열인 해시 테이블에 보관된다.

요소를 추가하는 경우엔 해시 함수로 배치할 버킷을 계산하고 이 버킷 안에 요소를 추가한다. 해시 함수의 속도는 빨라야 하므로 이 처리는 굉장히 빨리 이뤄진다.

요소를 찾는 경우에도 해시 함수로 만들어지는 숫자를 활용해 버킷을 찾은 뒤 버킷 내부에서 원하는 요소를 찾는다. 해시 함수는 같은 요소라면 같은 값을 리턴하므로 다른 버킷을 확인할 필요 없이 바로 원하는 게 들어 있는 버킷을 찾을 수 있다.

가변성 관련 문제

요소가 추가될 때만 해시 코드를 계산한다. 요소가 바뀌어도 해시 코드는 계산되지 않고 버킷 재배치도 이뤄지지 않는다.

그래서 기본적인 LinkedHashSet과 LinkedHashMap의 키는 한 번 추가한 요소를 바꿀 수 없다. 그래서 해시 등의 mutable 프로퍼티로 요소를 조합하는 자료구조에선 mutable 객체가 쓰이지 않는다. 따라서 Set, Map의 키로 mutable 요소를 쓰면 안 되며, 쓰더라도 요소를 바꿔선 안 된다.

hashCode의 규약

hashCode는 명확한 규약이 있다. 코틀린 1.3.11을 기준으로 공식적인 규약은 아래와 같다.

어떤 객체를 바꾸지 않았다면(equals에서 비교에 쓰인 정보가 수정되지 않는 이상) 
  hashCode는 여러 번 호출해도 그 결과가 항상 같아야 한다

equals의 실행 결과로 두 객체가 같다고 나온다면 hashCode의 결과도 같다고 나와야 한다

첫 번째 요구사항은 일관성 유지를 위해 hashCode가 필요하단 것이다. 두 번째 요구사항은 많은 개발자가 자주 잊는 것들 중 하나이므로 강조돼야 한다.

hashCode는 equals와 같이 일관성 있게 동작해야 한다. 즉 같은 요소는 반드시 같은 해시 코드를 가져야 한다. 그래서 코틀린은 equals 구현을 오버라이드할 때 hashCode도 같이 오버라이드하는 걸 추천한다.

hashCode 구현하기

일반적으로 data 한정자를 붙이면 코틀린이 알아서 적당한 equals, hashCode를 정의하므로 직접 정의할 일은 거의 없다. 다만 equals를 따로 정의했다면 반드시 hashCode도 함께 정의해야 한다. hashCode는 기본적으로 equlas에서 비교에 쓰이는 프로퍼티를 기반으로 해시 코드를 만들어야 한다.

hashCode 구현 시 가장 중요한 규칙은 언제나 equals와 일관된 결과가 나와야 한다는 것이다. 같은 객체면 언제나 같은 값을 리턴하게 만들어라.

Last updated