compareTo 메서드는 Any 클래스에 있는 메서드가 아닙니다. 이는 수학적인 부등식으로 변환되는 연산자입니다.
참고로, compareTo메서드는 Comparable<T> 인터페이스에도 들어있습니다. 어떤 객체가 이 인터페이스를 구현하고 있거나 compareTo라는 연산자 메서드를 갖고 있다는 의미는 해당 객체가 어떤 순서를 갖고 있으므로, 비교할 수 있다는 의미가 됩니다.
compareTo는 다음과 같이 동작해야 합니다.
- 비대칭적 동작: a >= b이고, b <= a 라면, a = b여야 합니다.
비교와 동등성 비교에서 어떠한 관계가 있어야 하며, 서로 일관성이 있어야 합니다. - 연속적 동작: a >= b이고, b>= c 라면, a >= c여야 합니다.
이러한 동작을 하지 못하면, 요소 정렬이 무한 반복에 빠질 수 있습니다. - 코넥스(connex)적 동작: 두 요소는 어떤 확실한 관계를 갖고 있어야 합니다.
즉, a >= b 또는 b >= a중에 적어도 하나는 항상 true여야 합니다.
compareTo를 따로 정의해야 할까?
코틀린에서 compareTo를 따로 정의해야 하는 상황은 거의 없습니다. 일반적으로 어떤 프로퍼티 하나를 기반으로 순서를 지정하는 것으로 충분하기 때문입니다.
class User(val name: String, val surname: String)
val names = listOf<User>(/* ... */)
val sorted = names.sortdBy { it.surname }
여러 프로퍼티를 기반으로 정렬해야 한다면 sortedWith 함수를 사용하면 됩니다.
(compareBy를 활용해서 비교기(comparator)를 만들어 사용합니다.)
아래의 코드는 surname으로 정렬하고 만약 surname이 같은 경우에는 name까지 비교해서 정렬합니다.
val sorted = names.sortdWith { comapreBy( {it.surname}, {it.name}) }
문자열은 알파벳과 숫자등의 순서가 있습니다. 따라서 내부적으로 Comparable<String>을 구현하고 있습니다. 텍스트는 일반적으로 알파벳 숫자 순서로 정렬해야 하는 경우가 많으므로 굉장히 유용합니다. 하지만 단점도 있습니다. 예를 들어 직관적이지 않은 부등호 기호를 기반으로 두 문자열을 비교할 경우 코드를 직관적으로 이해하는데 시간이 걸립니다.
반대로 자연스러운 순서를 갖는 객체들이 있습니다. 예를 들어 측정 단위, 날짜, 시간 등이 자연스러운 순서를 갖습니다. 객체가 자연스러운 순서인지 확실하지 않다면, 비교기(comparator)를 사용하는 것이 좋습니다. 또 이를 자주 사용한다면, 클래스에 companion 객체로 만들어 두는 것도 좋습니다.
class User(val name: String, val surname: String) {
// ...
companion object {
val DISPLAY_ORDER = compareBy(User::surname, User::name)
}
}
val sorted = names.sortedWith(User.DISPLAY_ORDER)
compareTo 구현하기
compareTo를 구현할 때 유용하게 활용할 수 있는 톱레벨 함수가 있습니다. 두 값을 단순하게 비교하기만 한다면, compareValues 함수를 다음과 같이 활용할 수 있습니다.
class User(
val name: String,
val surname: String
): Comparable<User> {
override fun compareTo(other: User): Int = compareValue(surname, other.surname)
}
더 많은 값을 비교하거나, 선택기를 활용해서 비교하고 싶다면 다음과 같이 compareValuesBy를 사용합시다.
class User(
val name: String,
val surname: String
): Comparable<User> {
override fun compareTo(other: User): Int = compareValuesBy(this, other, {it.surname}, {it.name})
}
compareTo함수는 다음 값을 리턴해야 합니다.
- 0: 리시버와 other가 같은 경우
- 양수: 리시버가 other보다 큰 경우
- 음수: 리시버가 other보다 작은 경우
'Kotlin' 카테고리의 다른 글
[Effective Kotlin] 44 - 멤버 확장 함수의 사용을 피하라 (1) | 2023.11.26 |
---|---|
[Effective Kotlin] 43 - API의 필수적이지 않는 부분을 확장 함수로 추출하라 (0) | 2023.11.19 |
[Effective Kotlin] 41 - hashCode의 규약을 지켜라 (0) | 2023.11.04 |
[Effective Kotlin] 40 - equals의 규약을 지켜라 (0) | 2023.10.29 |
[Effective Kotlin] 39 - 태그 클래스보다는 클래스 계층을 사용하라 (0) | 2023.10.22 |