반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

안드로이드 개발자 노트

[이펙티브 코틀린] Item42. compareTo의 규약을 지켜라 본문

Kotlin/이펙티브 코틀린

[이펙티브 코틀린] Item42. compareTo의 규약을 지켜라

어리둥절범고래 2024. 1. 13. 19:47
반응형

compareTo 메서드는 수학적인 부등식으로 변환되는 연산자입니다.

obj1 > obj2 // obj1.compareTo(obj2) > 0
obj1 < obj2 // obj1.compareTo(obj2) < 0
obj1 >= obj2 // obj1.compareTo(obj2) >= 0
obj1 <= obj2 // obj1.compareTo(obj2) <= 0

 

compareTo 메서드는 Comparable<T> 인터페이스에도 들어 있습니다.

어떤 객체가 이 인터페이스를 구현하고 있거나 compareTo라는 연산자 메서드를 갖고 있다는 의미는 해당 객체가 어떤 순서를 갖고 있으므로, 비교할 수 있다는 것입니다.

 

compareTo는 다음과 같이 동작해야 합니다.

 

  • 비대칭적 동작: a >= b 이고 b >= a 라면, a == b 여야 한다. 즉, 비교와 동등성 비교에 어떠한 관계가 있어야 하며, 서로 일관성이 있어야 한다.
  • 연속적 동작: a >= b 이고 b >= c 라면, a >= c 여야 한다. 마찬가지로 a > b 이고 b > c 라면, a > c 여야 한다. 이러한 동작을 하지 못하면, 요소 정렬이 무한 반복에 빠질 수 있다.
  • 코넥스적 동작: 두 요소는 어떤 확실한 관계를 갖고 있어야 한다. 즉, a >= b 또는 b >= a 중에 적어도 하나는 항상 true여야 한다. 두 요소 사이에 관계가 없으면, 퀵 정렬과 삽입 정렬 등의 고전적인 정렬 알고리즘을 사용할 수 없다.

 

 

compareTo를 따로 정의해야 할까?

 

일반적으로 어떤 프로퍼티 하나를 기반으로 순서를 지정하는 것으로 충분하여 compareTo를 따로 정의하는 상황은 거의 없습니다.

여러 프로퍼티를 기반으로 정렬해야 한다면, sortedWith 함수를 사용하면 됩니다.

이 함수는 compareBy를 활용해서 비교기(comparator)를 만들어서 사용합니다.

val sorted = names.sortedWith(compareBy({ it.surname }, { it.name }))

 

문자열은 알파벳과 숫자 등의 순서가 있는데, 내부적으로 Comparable<String>를 구현하고 있어서 순서 비교가 가능합니다.

// 이렇게 하지 마세요.
print("Kotlin" > "Java") // true
// "Kotlin".compareTo("Java") > 0

 

 

 

객체가 자연스러운 순서를 갖기 어렵다면, 비교기(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 =
        compareValues(surname, other.surname)
}

더 많은 값을 비교하거나, 선택기(selector)를 활용해서 비교하고 싶다면, 다음과 같이 comapreValuesBy를 사용합니다.

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

 

반응형