본문 바로가기

코틀린

코틀린: 연산자 오버로딩(3) - compareTo()

연산자 오버로딩(operator overloading)에 관한 보충 설명입니다. 자세한 내용은 쉽게 다가가는 최신 프로그래밍: 코틀린 - 2.2 연산자 오버로딩을 참고하기 바랍니다. 연산자 오버로딩에서 무심코 지나치면 안되는 내용 중 복합 대입연산자equals()에 대해 알아보았습니다. 마지막으로 compareTo()에 대해 알아보겠습니다.

■ Comparable 인터페이스 에서 선언한 추상 메소드 compareTo()
compareTo()는 Comparable 인터페이스에서 선언한 추상 메소드입니다.  compareTo() 앞에 키워드 operator가 붙었습니다. 즉, compareTo()는 연산자 오버로딩을 위한 추상 메소드입니다. compareTo()는 Int 타입을 반환 합니다. 

public interface Comparable<in T> {
     public operator fun compareTo(other: T): Int
}

 

■ 사용자 정의 클래스 Person에서 compareTo() 구현
compareTo()는 비교 연산자(<, <=, >, >=)를 사용했을 때 호출되는 함수입니다.  사용자 정의 클래스 Person에서 비교 연산자 오버로딩을 위해 compareTo()를 정의하려면, 인터페이스 Comparable을 구현 상속받아야 합니다.  자식 클래스 Person에서 compareTo()를 재정의(override)할 때 키워드 operator는 생략할 수 있습니다.

아래 예는 Person 타입 객체의 속성 중 나이(age)만을 비교합니다. 수신 객체(this)의 나이가 비교 대상 객체(other)와 같으면 0을 반환합니다. 

class Person(val name: String, val age: Int): Comparable<Person> {
    override fun compareTo(other: Person): Int {
        return this.age - other.age  // this는 생략할 수 있습니다.
    }
}

 

■ compareTo() 구현 - 단일 속성 비교: 기본 클래스의 compareTo() 사용
위 코드를 조금 수정해 볼까요? 속성 age의 타입은 기본 타입(primitive type)인 Int 입니다. 따라서, Int 클래스에서 정의한 compareTo()를 호출할 수 있습니다.

class Person(val name: String, val age: Int): Comparable<Person> {
    override fun compareTo(other: Person): Int {
        return this.age.compareTo(other.age)  // this는 생략할 수 있습니다.
    }
}

 

■ compareTo() 구현 - 여러 속성을 순차 비교: 기본 클래스의 compareTo() 사용
속성 중 name은 비교하지 않고 age만 비교하는 게 이상하죠. 위 코드를 제대로 수정해볼까요? 속성 name과 age가 모두 기본 타입(String, Int)이어서 각 클래스에서 정의한 compareTo() 함수를 호출할 수 있습니다(뺄셈을 사용해도 됩니다).

속성 name을 먼저 비교하고, 이 속성이 같으면(nameCompare가 0) 속성 age를 비교합니다. 속성이 여러 개이면, 우선 순위에 따라 차례대로 비교해야 합니다.

data class Person(val name: String, val age: Int): Comparable<Person> {
    override fun compareTo(other: Person): Int {
        val nameCompare = this.name.compareTo(other.name)
        if (nameCompare != 0)
            return nameCompare
        return this.age.compareTo(other.age)
    }
}

 

■ compareTo() 구현 - 여러 속성을 순차 비교: compareValuesBy() 사용
비교할 속성이 3개 이상이라면, 위 코드는 조금 불편하겠죠. 여러 속성을 우선 순위에 따라 순차 비교할 때는  compareValuesBy() 함수를 사용하는 것이 편리합니다.  compareValuesBy() 함수의 처음 2개 파라미터는 2개의 비교 대상 객체이며, 3번째 파라미터는 가변인자(vararg)입니다. 3번째 파라미터부터 시작해서 비교 우선 순위에 따라 클래스의 속성을 차례로 전달하면 됩니다. 아래 예는 Person 클래스의 속성 중 이름(Person::name)을 먼저 비교하고, 이름이 같으면 나이(Person::age)를 비교하도록 지정했습니다. 코드가 확~ 줄었습니다.

class Person(val name: String, val age: Int): Comparable<Person> {
    override fun compareTo(other: Person): Int {
        return compareValuesBy(this, other, Person::name, Person::age)
    }
}

 

■ 컬렉션 객체의 sorted() 함수도 내부적으로 compareTo()를 호출
한 가지 더!, 컬렉션 객체의 원소를 정렬할 때 사용하는 함수 sorted()도 내부적으로 compareTo()를 호출합니다. 리스트 컬렉션 people의 원소를 이름 순(올림차순)으로 정렬하고, 이름이 같으면 나이 순으로 정렬합니다. compareTo()에서 정의한 정렬 기준이 적용된 것입니다. compareTo() 블록에 println("call compareTo() ... ")을 넣어서 확인해 보세요!

data class Person(val name: String, val age: Int): Comparable<Person> {
    override fun compareTo(other: Person): Int {
        return compareValuesBy(this, other, Person::name, Person::age)
    }
}

fun main() {
    val people = listOf(
        Person("Kim", 24), Person("Kim", 22), Person("Park", 25))

    val sortedPeople = people.sorted()
    println(sortedPeople)
    // [Person(name=Kim, age=22), Person(name=Kim, age=24), Person(name=Park, age=25)]
}