(Kotlin/코틀린) Comparable & Comparator 구현
1. Comparable — 자연 정렬 순서 정의
Comparable<T>를 구현하면 sorted(), <, > 등을 바로 사용할 수 있습니다.
data class Student(val name: String, val score: Int) : Comparable<Student> {
override fun compareTo(other: Student): Int {
return this.score.compareTo(other.score) // 점수 오름차순
}
}
val students = listOf(
Student("Alice", 90),
Student("Bob", 75),
Student("Charlie", 85)
)
println(students.sorted())
// [Student(name=Bob, score=75), Student(name=Charlie, score=85), Student(name=Alice, score=90)]
println(students.min()) // Student(name=Bob, score=75)
println(students.max()) // Student(name=Alice, score=90)
compareTo 반환 규칙
override fun compareTo(other: T): Int {
// 음수 → this가 other보다 작음 (앞에 위치)
// 0 → 같음
// 양수 → this가 other보다 큼 (뒤에 위치)
}
2. compareValuesBy — 간결한 compareTo 구현
여러 필드를 기준으로 비교할 때 compareValuesBy를 사용합니다.
data class Employee(
val department: String,
val name: String,
val salary: Int
) : Comparable<Employee> {
override fun compareTo(other: Employee): Int =
compareValuesBy(this, other,
{ it.department }, // 1차: 부서 이름 오름차순
{ it.salary }, // 2차: 급여 오름차순
{ it.name } // 3차: 이름 오름차순
)
}
3. Comparator — 외부에서 정렬 기준 정의
Comparable을 구현하지 않고도 정렬 기준을 유연하게 지정합니다.
data class Product(val name: String, val price: Int, val rating: Double)
val products = listOf(
Product("노트북", 1200000, 4.5),
Product("마우스", 35000, 4.8),
Product("키보드", 85000, 4.2),
Product("모니터", 450000, 4.7)
)
// 가격 오름차순
val byPrice = Comparator<Product> { a, b -> a.price.compareTo(b.price) }
println(products.sortedWith(byPrice).map { it.name })
// [마우스, 키보드, 모니터, 노트북]
// 평점 내림차순
val byRatingDesc = Comparator<Product> { a, b -> b.rating.compareTo(a.rating) }
println(products.sortedWith(byRatingDesc).map { "${it.name}(${it.rating})" })
// [마우스(4.8), 모니터(4.7), 노트북(4.5), 키보드(4.2)]
4. compareBy / compareByDescending — 간결한 Comparator
// 단일 기준
val byPrice = compareBy<Product> { it.price }
val byPriceDesc = compareByDescending<Product> { it.price }
// 다중 기준 (1차, 2차, 3차...)
val multiSort = compareBy<Product>(
{ it.rating * -1 }, // 평점 내림차순 (음수 처리)
{ it.price } // 가격 오름차순
)
println(products.sortedWith(multiSort).map { it.name })
5. thenBy / thenByDescending — 정렬 기준 체이닝
val sorted = products.sortedWith(
compareByDescending<Product> { it.rating }
.thenBy { it.price } // 평점 같으면 가격 오름차순
.thenBy { it.name } // 가격도 같으면 이름 오름차순
)
sorted.forEach { println("${it.name}: 평점=${it.rating}, 가격=${it.price}") }
6. sortedBy / sortedByDescending — 단순 정렬
단일 기준 정렬은 이 함수들이 가장 간결합니다.
// 단일 기준
val byName = products.sortedBy { it.name }
val byPriceDesc = products.sortedByDescending { it.price }
// 문자열 정렬
val names = listOf("Charlie", "alice", "Bob")
println(names.sorted()) // [Bob, Charlie, alice] — 대소문자 구분
println(names.sortedBy { it.lowercase() }) // [alice, Bob, Charlie] — 대소문자 무시
7. 실전 패턴 — 복합 정렬 (쇼핑몰)
enum class SortOption { PRICE_ASC, PRICE_DESC, RATING_DESC, NAME_ASC }
fun sortProducts(products: List<Product>, option: SortOption): List<Product> {
return when (option) {
SortOption.PRICE_ASC -> products.sortedBy { it.price }
SortOption.PRICE_DESC -> products.sortedByDescending { it.price }
SortOption.RATING_DESC -> products.sortedWith(
compareByDescending<Product> { it.rating }.thenBy { it.price }
)
SortOption.NAME_ASC -> products.sortedBy { it.name.lowercase() }
}
}
8. 실전 패턴 — 우선순위 큐
import java.util.PriorityQueue
data class Task(val name: String, val priority: Int, val deadline: Long)
// 우선순위 높은 것(숫자 클수록) → 마감 빠른 것 순
val taskQueue = PriorityQueue<Task>(
compareByDescending<Task> { it.priority }
.thenBy { it.deadline }
)
taskQueue.add(Task("이메일 답장", 1, System.currentTimeMillis() + 3600000))
taskQueue.add(Task("버그 수정", 3, System.currentTimeMillis() + 1800000))
taskQueue.add(Task("문서 작성", 2, System.currentTimeMillis() + 7200000))
while (taskQueue.isNotEmpty()) {
val task = taskQueue.poll()
println("처리: ${task.name} (우선순위: ${task.priority})")
}
// 처리: 버그 수정 (우선순위: 3)
// 처리: 문서 작성 (우선순위: 2)
// 처리: 이메일 답장 (우선순위: 1)
9. naturalOrder / reverseOrder
val numbers = listOf(3, 1, 4, 1, 5, 9, 2, 6)
println(numbers.sortedWith(naturalOrder())) // [1, 1, 2, 3, 4, 5, 6, 9]
println(numbers.sortedWith(reverseOrder())) // [9, 6, 5, 4, 3, 2, 1, 1]
// nullable 정렬 — null을 맨 앞 또는 맨 뒤로
val nullables = listOf("banana", null, "apple", null, "cherry")
println(nullables.sortedWith(nullsFirst(naturalOrder())))
// [null, null, apple, banana, cherry]
println(nullables.sortedWith(nullsLast(naturalOrder())))
// [apple, banana, cherry, null, null]
10. 정리
| 방법 | 용도 |
|---|---|
Comparable.compareTo |
클래스의 기본 정렬 순서 정의 |
compareValuesBy |
여러 필드 기준 compareTo 구현 |
compareBy { } |
단/다중 기준 Comparator 생성 |
thenBy / thenByDescending |
정렬 기준 체이닝 |
sortedBy { } |
단일 기준 즉석 정렬 |
nullsFirst / nullsLast |
null 포함 컬렉션 정렬 |
- 클래스의 고유 순서가 있으면
Comparable구현 - 상황에 따라 다른 정렬이 필요하면
Comparator사용