안드로이드 개발자 노트
[이펙티브 코틀린] Item41. hashCode의 규약을 지켜라 본문
해시 테이블은 각 요소에 숫자를 할당하는 함수가 필요합니다.
이 함수를 해시 함수라고 부르며, 같은 요소라면 항상 같은 숫자를 리턴합니다.
해시 함수는 빠르고 충돌이 적을 수록 좋습니다.
가변성과 관련된 문제
요소가 추가될 때만 해시 코드를 계산합니다.
요소가 변경되어도 해시 코드는 계산되지 않으며, 버킷 재배치도 이루어지지 않습니다.
그래서 기본적인 LinkedHashSet와 LinkedHashMap의 키는 한 번 추가한 요소를 변경할 수 없습니다.
세트와 맵의 키로 mutable 요소를 사용하면 안되고, 사용하더라도 요소를 변경해서는 안됩니다.
data class FullName(
var name: String,
var surname: String
)
val person = FullName("Maja", "Markiewicz")
val s = mutableSetOf<FullName>()
s.add(person)
person.surname = "Moskala"
print(person) // FullName(name=Maja, surnam=Moskala)
print(person in s) // false
print(s.first() == person) // true
hashCode의 규약
hashCode는 명확한 규약이 있습니다.
- 어떤 객체를 변경하지 않았다면, hashCode는 여러 번 호출해도 그 결과가 항상 같아야 한다.
- equals 메서드의 실행 결과도 두 객체가 같다고 나온다면, hashCode 메서드의 호출 결과도 같다고 나와야 한다.
두 번째 요구 사항은 'hashCode는 equals와 같이 일관성 있는 동작을 해야 한다'는 것을 의미합니다.
즉, 같은 요소는 반드시 같은 해시 코드를 가져야 하며, 그렇지 않으면 컬렉션 내부에 요소가 들어 있는지 제대로 확인하지 못하는 문제가 발생할 수 있습니다.
그래서 코틀린은 equals 구현을 오버라이드할 때, hashCode도 함께 오버라이드하는 것을 추천합니다.
hashCode 구현하기
equals를 따로 정의했다면, 반드시 hashCode도 함께 정의해 줘야 합니다.
equals를 따로 정의하지 않았다면, 정당한 이유가 없는 이상 hashCode를 따로 정의하지 않는 것이 좋습니다.
hashCode는 기본적으로 equals에서 비교에 사용되는 프로퍼티를 기반으로 해시코드를 만들어야 합니다.
다음은 일반적인 equals와 hashCode의 구현 예입니다.
class DateTime(
private var millis: Long = 0L,
private var timeZone: TimeZone? = null
) {
private var asStringCache = ""
private var changed = false
override fun equals(other: Any?): Boolean =
other is DateTime &&
other.millis == millis &&
other.timeZone == timeZone
override fun hashCode(): Int {
var result = millis.hashCode()
result = result * 31 + timeZone.hashCode() // 관례적으로 31을 사용한다.
return result
}
}
hashCode를 구현할 때 가장 중요한 규칙은 '언제나 equals와 일관된 결과가 나와야 한다'입니다.
같은 객체라면 언제나 같은 값을 리턴하게 만들어야 합니다.
'Kotlin > 이펙티브 코틀린' 카테고리의 다른 글
[이펙티브 코틀린] Item42. compareTo의 규약을 지켜라 (0) | 2024.01.13 |
---|---|
[이펙티브 코틀린] Item40. equals의 규약을 지켜라 (1) | 2024.01.13 |
[이펙티브 코틀린] Item39. 태그 클래스보다는 클래스 계층을 사용하라 (0) | 2023.12.31 |