Comparable과 Comparator 인터페이스에 대한 보충 설명입니다. 자세한 내용은 쉽게 다가가는 최신 프로그래밍: 코틀린 - 5.12 정렬을 참고하기 바랍니다.
2개 인터페이스 모두 '비교(compare)'라는 이름을 갖고 있어 도대체 뭐가 다를까 궁금하죠. 가장 큰 차이점은 Comparable은 일반 클래스에서 구현 상속을 받아 사용하며, Comparator는 람다 식으로 다양한 방식의 비교 함수를 구현할 때 사용합니다.
■ Comparable 인터페이스와 추상 메소드 compareTo()
먼저 Comparable 인터페이스를 상속받아 추상 메소드 compareTo()를 구현한 예입니다. compareTo() 메소드는 수신자 객체(this)와 비교 대상 객체(other)의 name 속성을 비교합니다. p1 < p2는 Boolean 값(여기서는 false)를 반환하지만, p1.compareTo(p2)는 p1이 p2보다 크므로 양의 정수를 반환합니다. 다른 속성을 비교하려면 p1.studentNo < p2.studentNo 처럼 비교 대상인 속성을 지정해야 합니다.
class Person(val name: String, val studentNo: Int,
var age: Int) : Comparable<Person> {
override fun compareTo(other: Person) =
this.name.compareTo(other.name)
}
fun main() {
val p1 = Person("홍길동", 20210101, 23)
val p2 = Person("신사임당", 20230101, 21)
println(p1 < p2) // false
println(p1.compareTo(p2)) // 양의 정수
println(p1.studentNo < p2.studentNo) // true
}
■ Comparator 인터페이스와 추상 메소드 compare()
이번에는 위의 예제를 Comparator 인터페이스를 사용해 구현해 보겠습니다. Person을 data 클래스로 선언하고, Comparator 인터페이스를 상속받은 클래스(AgeComparator)는 비교 기능만 갖습니다. Comparator 인터페이스에서 선언한 추상 메소드 compare()는 2개의 객체를 형식 인자로 전달받습니다. 이 메소드를 구현할 때 compareTo()를 사용하거나 비교 대상이 숫자 타입인 경우 뺄셈 연산을 사용할 수 있습니다.
data class Person(val name: String, val studentNo: Int, var age: Int)
class AgeComparator : Comparator<Person> {
override fun compare(p1: Person, p2: Person) = p1.age.compareTo(p2.age)
// override fun compare(p1: Person, p2: Person) = p1.age - p2.age
// 숫자 타입 속성은 뺄셈 연산을 사용할 수 있음.
}
fun main() {
val p1 = Person("홍길동", 20210101, 23)
val p2 = Person("신사임당", 20230101, 21)
val ageCompare = AgeComparator()
println(ageCompare.compare(p1, p2)) // p1.age > p2.age 보다 크면 양의 정수.
}
■ 람다 식을 사용한 Comparator 구현
실제 응용에서 Comparator 인터페이스는 위의 예제처럼 독립적인 클래스로 구현하지 않고 람다 식을 사용합니다. 책에서는 무명 객체를 사용한 방법도 함께 설명하고 있습니다.
data class Person(val name: String, val studentNo: Int, var age: Int)
val ageCompare = Comparator<Person> { p1, p2 ->
p1.age - p2.age // p1.age.compareTo(p2.age)
}
fun main() {
val p1 = Person("홍길동", 20210101, 23)
val p2 = Person("신사임당", 20230101, 21)
println( ageCompare.compare(p1, p2))
}
■ List 컬렉션 + Comparator + sortedWith
Comparator를 사용한 대표적 응용은 리스트 컬렉션 객체를 정렬하는 예제입니다. 표준 함수 sortedWith()의 인자로 ageCompare 객를 전달하면 됩니다.
data class Person(val name: String, val studentNo: Int, var age: Int)
val ageCompare = Comparator<Person> { p1, p2 ->
p1.age - p2.age
}
fun main() {
val heroes = listOf(
Person("홍길동", 20210101, 23) ,
Person("신사임당", 20230101, 21))
val sortedHeroes = heroes.sortedWith(ageCompare)
for (hero in sortedHeroes) {
println("${hero.name}, ${hero.age}")
}
}
■ List 컬렉션 + sortedWith + compareBy
지금까지 구현한 건 사실 표준 라이브러리 함수 compareBy를 사용하면 한 줄로 구현할 수 있습니다. 이렇게 억울할 수가요!! 억울하다고 생각하지 마세요. 하나씩 익혀가는 과정입니다.
val sortedPeople = heroes.sortedWith(compareBy { it.age })
■ 여러 개 조건을 비교하는 Comparator 구현
"나이가 같으면 이름 순으로 정렬"하는 식으로 여러 개 조건을 단계별로 비교하는 Comparator를 구현할 수 있습니다.
data class Person(val name: String, val studentNo: Int, var age: Int)
class AgeNameComparator : Comparator<Person> {
override fun compare(p1: Person, p2: Person): Int {
val ageCompare = p1.age.compareTo(p2.age)
return if (ageCompare != 0) {
ageCompare
} else {
p1.name.compareTo(p2.name)
}
}
}
fun main() {
val p1 = Person("홍길동", 20210101, 21)
val p2 = Person("신사임당", 20230101, 21)
val ageNameCompare = AgeNameComparator()
println(ageNameCompare.compare(p1, p2))
}
위 코드도 두 줄 코드로 끝낼 수 있습니다.
data class Person(val name: String, val studentNo: Int, var age: Int)
fun main() {
val heroes = listOf(
Person("홍길동", 20210101, 21) ,
Person("신사임당", 20230101, 21))
val multiCompare = compareBy<Person> { it.age }.thenBy { it.name }
val sortedHeroes = heroes.sortedWith(multiCompare)
for (hero in sortedHeroes) {
println("${hero.name}, ${hero.age}")
}
}
'코틀린' 카테고리의 다른 글
코틀린: 5장 테스트에 도전해 보세요 (0) | 2025.01.09 |
---|---|
코틀린: 시퀀스(Sequence)와 컬렉션(Collection)은 뭐가 다를까요? (0) | 2025.01.09 |
코틀린: fold와 reduce - 누적 합과 누적 곱 (0) | 2025.01.09 |
코틀린: 한 걸음 뒤에서 컬렉션의 숲을 바라볼까요? (0) | 2025.01.09 |
코틀린: 4장 테스트에 도전해 보세요(3,4,5번 정답 해설) (0) | 2025.01.09 |