이펙티브 코틀린 Item 42: compareTo의 규약을 지켜라

woga·2023년 11월 10일
0

코틀린 공부

목록 보기
45/54
post-thumbnail

compareTo 메서드는 Any 클래스에 있는 메서드가 아니다. 부등식으로 변환되는 연산자다.

참고로 compareTo 메서드는 Comparable<T> 인터페이스에도 들어 있다.
어떤 객체가 이 인터페이스를 구현하고 있거나 compareTo라는 연산자 메서드를 갖고 있다는 의미는 해당 객체가 어떤 순서를 갖고 있으므로 비교할 수 있다.

이 메서드는 다음과 같이 동작해야 한다.

  • 비대칭적 동작: a >= b, b >= a, a == b여야 한다. 서로 일관성이 있어야 한다.

  • 연속적 동작: a >= b, b >= c, a >= c 여야 한다. 이런 동작을 못하면 무한 반복에 빠질 수 있다.

  • 코넥스적 동작 : 두 요소는 확실한 관계를 갖고 있어야 한다. 즉, a >= b 또는 b >= a 중에 적어도 하나는 항상 true 여야 한다. 두 요소 사이에 관계가 없으면, 퀵 정렬과 삽입 정렬 등의 고전적인 정렬 알고리즘을 사용할 수 없다. 대신 위상 정렬과 같은 정렬 알고리즘만 사용할 수 있다.

compareTo 따로 정의해야 할까?

따로 정의해야하는 상황은 거의 없다. 일반적으로 어떤 프로퍼티 하나를 기반으로 순서를 지정하는 것으로 충분하기 때문이다.

ex) sorteBy를 쓴다던지, sortedWith을 쓴다던지 등

fun main() {
    val names = listOf<User>(/*...*/)
    val sorted = names.sortedBy { it.surname }
}

class User(val name: String, val surname: String)
val sorted = names.sortedWith(compareBy({ it.surname }, {it.name}))

또한 문자열은 알파벳과 숫자 등의 순서가 있다. 따라서 내부적으로 Comparable<String>을 구현하고 있다. 텍스트는 일반적으로 알파벳과 숫자 순서로 정렬해야 하는 경우가 많으므로 굉장히 유용하다.

// don't do that
print("Kotlin" > "Java") // true

자연스러운 순서를 갖는 객체들이 있다. 객체가 자연스러운 순서인지 확실하지 않다면 비교기(comparator)를 사용하는 것이 좋다. 이를 자주 사용한다면 클래스 companion 객체로 만들어 두자.

fun main() {
    val names = listOf<User>(/*...*/)
    val sorted = names.sortedWith(User.DISPLAY_ORDER)
}

class User(val name: String, val surname: String) {
    companion object {
        val DISPLAY_ORDER = compareBy(User::surname, User::name)
    }
}

compareTo 구현하기

compareTo를 구현할 때 유용하게 활용할 수 있는 톱레벨 함수가 있다.
만약 두 값을 단순하게 비교하기만 하면 compareValues 함수를 활용할 수 있다.

class User(val name: String, val surname: String): Comparable<User> {
	override fun compareTo(other: User): Int = compareValues(surname, other.surname)
}

또한 더 많은 값을 비교하거나 선택기(selector)를 활용해서 비교하고 싶다면 compareValuesBy를 사용하자.

class User(val name: String, val surname: String): Comparable<User> {
	override fun compareTo(other: User): Int = compareValuesBy(this, other, { it.surname}, { it.name })
}

특별한 논리를 구현해야한다면 다음 값을 리턴해야 한다는 것을 잊지 말자

  • 0: 리시버와 other이 같음
  • 양수: 리시버가 other보다 큰 경우
  • 음수: 리시버가 other보다 작은 경우

그리고 비대칭적 동작, 연속적 동작, 코넥스적 동작을 하는지 확인하자.

profile
와니와니와니와니 당근당근

0개의 댓글