Comparable, Comparator파헤치기

SSY·2023년 2월 20일
0

Kotlin

목록 보기
8/8
post-thumbnail

시작하며

이름이 참으로 비슷하여 오묘하게 헷갈리는 두 녀석, Comparable과 Comparator. 이번 포스팅은 이 두 인터페이스의 차이와 사용 실례에 대해 알아보고자 한다.

1. Comparable

1.1. 기본 사용법

이해의 첫걸음은 연상 학습법이 좋다. Comparable은 사전적인 의미로 '비교할 수 있는'이라는 뜻을 가지고 있다. 따라서, Comparable을 통하여 비교를 할 수 있다는 것을 유추할 수 있다. 아래와 같이 말이다.

val person1 = Person(name = "GilDong", age = 10)
val person2 = Person(name = "Michol", age = 20)

Log.i("comparableResult", "${person1 >= person2}")
Log.i("comparableResult", "${person1 > person2}")
Log.i("comparableResult", "${person1 == person2}")
Log.i("comparableResult", "${person1 < person2}")
Log.i("comparableResult", "${person1 <= person2}")

이는 '숫자 뿐만이 아닌, 참조 자료형'에도 대소 비교를 수행할 수 있다는걸 뜻한다. 즉, '비교할 수 있는'이라는 말과 맞다. 위와 같이 비교를 할 수 있으려면 어떻게 해야할까? 바로, 'Comparable을 구현'해야 한다.

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

이제 빌드해보면?

위 사진에서 보는바와 같이 '참조 자료형인데도 불구하고'비교 연산자를 사용하여 객체를 비교하는것을 볼 수 있다. 이렇게 요약할 수 있다.

Comparable객체는 객체의 대소비교를 하기 위해 사용된다.

1.2. compareValuesBy

compareValuesBy라는 코틀린 표준 API는 Comparable을 사용할때 알아두면 좋은 함수이다. 해당 함수는 다음과 같은 파라미터 특성을 가지고 있다.

파라미터 특성

  • 첫 번째 파라미터 : 비교를 수행하는 왼쪽의 객체
  • 두 번째 파라미터 : 비교를 수행하는 오른쪽의 객체
  • 세 번째 이상의 가변 파라미터 : 비교의 기준이 되는 객체

따라서 아래와 같이 비교를 진행할 수도 있다.

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

이는 첫 번째 기준으로 age를 비교하겠다는 뜻이다. 만약 age가 같다면? 그 후엔 name을 기준으로 비교하겠다는 뜻이다.

1.3. 그 외, Comparable의 신기한 점?

val bool1 = true
val bool2 = false
Log.i("comparableResult", "bool : ${bool1 > bool2}")

val str1 = "a"
val str2 = "b"
Log.i("comparableResult", "str : ${str1 > str2}")

val char1 = 'a'
val char2 = 'b'
Log.i("comparableResult", "char : ${char1 > char2}")

val float1 = 1.0f
val float2 = 2.0f
Log.i("comparableResult", "float : ${float1 > float2}")

val double1 = 1.0
val double2 = 2.0
Log.i("comparableResult", "double : ${double1 > double2}")

int형이 아닌 자료형에 대소비교 수행시, 비교가 된다? 신기한 점이다. build를 해보자. (어쩌면 double이나 float은 대소비교가 되는 것이 자연스럽게 느껴지겠다.)

사진에서 보시다시피 대소비교가 된다는 것을 알 수 있다. 어떻게 이것이 가능할까? 그 이유를 알기 위해선 다음 사실을 알아야 한다.

Kotlin의 Boolean, String, Char, Float, Double은 모두 Kotlin Wrapper클래스이다. 그리고 이 객체들은 모두 Comparable을 구현했다.
[Boolean]

[Char]

[String]

[Float]

[Double]

2. Comparator

Comparator는 사전적 의미로 '비교 측정기'라는 뜻을 가지고 있다. 즉, Comparator를 구현한 객체를 통해 값을 비교하고 해당 값을 측정해주는 객체란 뜻으로 줄여서 '정렬'에 사용된다는 뜻이다.

2.1. 기본 사용법

아래 Person객체를 사용하여 Comparator를 수행할 것이다.

data class Person(val name: String, val age: Int, val tel: String) {
    companion object {
        val comparatorByName = compareBy(Person::name)
        val comparatorByAge = compareBy(Person::age)
        val comparatorByTel = compareBy(Person::tel)
    }
}

여기서 compareBy를 모르는 사람을 위해 간단히 설명해볼까 한다. 해당 메소드는 Comparator객체를 반환하는 메서드다.

이제 Comparator를 정의하였으니, sort를 해보자.

[참고]
Kotlin에서 sort할때 주로 쓰는 표준 API는 sortWith와 sortedWith가 있다.

아래 6개의 Person객체들을 Comparator를 사용하여 정렬한다.

val person6 = Person(name = "F", age = 11, "010-3000-0000")
val person3 = Person(name = "C", age = 10, "010-4000-0000")
val person5 = Person(name = "E", age = 15, "010-2000-0000")
val person4 = Person(name = "D", age = 12, "010-1000-0000")
val person2 = Person(name = "B", age = 13, "010-5000-0000")
val person1 = Person(name = "A", age = 14, "010-6000-0000")

이제 이 객체들을 이제 정렬(=sort)해보자.

val sortedPersonByName = persons.sortedWith(Person.comparatorByName)
val sortedPersonByAge = persons.sortedWith(Person.comparatorByAge)
val sortedPersonByTel = persons.sortedWith(Person.comparatorByTel)

Log.i("ComparatorResult", "byName : $sortedPersonByName")
Log.i("ComparatorResult", "byAge : $sortedPersonByAge")
Log.i("ComparatorResult", "byTel : $sortedPersonByTel")
byName : [Person(name=A, age=14, tel=010-6000-0000), Person(name=B, age=13, tel=010-5000-0000), Person(name=C, age=10, tel=010-4000-0000), Person(name=D, age=12, tel=010-1000-0000), Person(name=E, age=15, tel=010-2000-0000), Person(name=F, age=11, tel=010-3000-0000)]
byAge : [Person(name=C, age=10, tel=010-4000-0000), Person(name=F, age=11, tel=010-3000-0000), Person(name=D, age=12, tel=010-1000-0000), Person(name=B, age=13, tel=010-5000-0000), Person(name=A, age=14, tel=010-6000-0000), Person(name=E, age=15, tel=010-2000-0000)]
byTel : [Person(name=D, age=12, tel=010-1000-0000), Person(name=E, age=15, tel=010-2000-0000), Person(name=F, age=11, tel=010-3000-0000), Person(name=C, age=10, tel=010-4000-0000), Person(name=B, age=13, tel=010-5000-0000), Person(name=A, age=14, tel=010-6000-0000)]

로그가 좀 길어서 가독성이 안좋을 수도 있지만, 그래도 찬찬히 보면 금방 이해된다. Name을 기준으로 정렬한 경우는 Name을 기준으로 오름차순으로 정렬되었다.

age와 tel도 마찬가지다.

[정리]
Comparator는 리스트 객체를 sort하기 위해 사용된다. 이때 Kotlin에서 주로 사용되는 객체는 sortWith 또는 sortedWith를 많이 사용한다. 해당 메서드 인자로 comparator객체를 주입해주면 된다.

3. 마치며

은근히 많이 보이지만, 알려고 하지 않으면 계속 헷갈릴 수 있는 Comparable, Comparator에 대해 알아봤다. 해당 포스팅으로 많은 도움이 되었으면 한다.

[마지막 초 간단 요약]
Comparable : 객체의 대소 비교를 위해 사용
Comparator : 객체의 정렬을 위해 사용

profile
불가능보다 가능함에 몰입할 수 있는 개발자가 되기 위해 노력합니다.

0개의 댓글