1. 引言

Kotlin 作为一门现代 JVM 语言,在集合操作和函数式编程方面提供了非常优雅的 API。其中,Comparator 是实现自定义排序逻辑的核心工具之一。本文将深入探讨 Kotlin 中 Comparator 的使用方式,帮助你在实际开发中写出更简洁、更具表达力的排序代码。

无论你是处理字符串、数值,还是复杂的数据类(data class),掌握 Comparator 都能让你轻松应对多字段排序、逆序排列等常见需求。✅

2. 什么是 Comparator 接口

Comparator<T> 是一个用于定义对象之间比较规则的接口。当默认的自然排序(natural order)无法满足需求时,我们就可以通过实现或构造 Comparator 来控制排序行为。

它的核心方法是 compare(a: T, b: T): Int,返回值含义如下:

  • ✅ 返回 0:表示两个对象相等
  • ✅ 返回 负数:表示第一个对象小于第二个
  • ✅ 返回 正数:表示第一个对象大于第二个

只要遵循这个约定,你就能构建出可靠的排序逻辑。

⚠️ 注意:不要手动创建复杂的比较逻辑来违反这一规则,否则可能导致排序结果不稳定甚至崩溃。

3. compare() 方法详解

compare()Comparator 接口的抽象方法,所有自定义比较器都基于它实现。

3.1 使用 compare() 实现基础排序

假设我们要按字符串长度对单词列表进行排序:

val words = listOf("apple", "blueberry", "cherry", "date")

val lengthComparator = object : Comparator<String> {
    override fun compare(string1: String, string2: String): Int {
        return string1.length - string2.length
    }
}

assertIterableEquals(
    listOf("date", "apple", "cherry", "blueberry"),
    words.sortedWith(lengthComparator)
)

上面这段代码虽然功能正确,但写法偏冗长——这在 Kotlin 中属于“踩坑”写法 ❌。Kotlin 提供了更简洁的顶层函数来替代这种匿名对象方式。

3.2 使用顶层函数简化代码

Kotlin 标准库提供了 compareBy 这类高阶函数,可以直接内联创建 Comparator,大幅提升可读性:

val words = listOf("apple", "blueberry", "cherry", "date")

assertIterableEquals(
    listOf("date", "apple", "cherry", "blueberry"),
    words.sortedWith(compareBy { it.length })
)

✅ 简洁明了!一行搞定长度排序。

同样地,对于自定义类型也可以轻松扩展:

data class Person(val name: String, val age: Int)

val people = listOf(Person("Alice", 29), Person("Bob", 31), Person("Bob", 29))

assertIterableEquals(
    listOf(Person("Alice", 29), Person("Bob", 29), Person("Bob", 31)),
    people.sortedWith(compareBy({ it.age }, { it.name }))
)

这里使用 compareBy 支持多个选择器(selector),先按年龄升序,再按姓名升序排列。

4. Comparator 扩展函数进阶用法

Kotlin 不仅提供了基础的 compareBy,还通过一系列扩展函数让 Comparator 具备链式组合能力,极大增强了表达力。

4.1 reversed():反转排序顺序

想把升序变降序?直接调用 .reversed() 即可:

val words = listOf("apple", "blueberry", "cherry", "date")

assertIterableEquals(
    listOf("blueberry", "cherry", "apple", "date"),
    words.sortedWith(compareBy<String> { it.length }.reversed())
)

⚠️ 注意泛型参数 <String> 必须显式指定,否则编译器无法推断上下文。

4.2 then():链式组合多个 Comparator

当你需要多级排序时,可以用 then() 将多个 Comparator 组合起来:

data class Person(val name: String, val age: Int)
val people = listOf(Person("Alice", 29), Person("Bob", 31), Person("Cleo", 29))

val sortedPeople = people.sortedWith(
    compareBy(Person::age).then(compareBy(Person::name))
)

assertIterableEquals(
    listOf(Person("Alice", 29), Person("Cleo", 29), Person("Bob", 31)),
    sortedPeople
)

逻辑清晰:先按年龄排序,年龄相同再按名字排序。

4.3 thenBy():更 idiomatic 的链式写法

相比 then(compareBy(...))thenBy() 更简洁,因为它接受的是属性选择器而非完整的 Comparator

val sortedPeople = people.sortedWith(
    compareBy(Person::age).thenBy(Person::name)
)

// 结果同上

✅ 推荐写法!语法更自然,减少嵌套。

4.4 thenDescending() 与 thenByDescending()

如果某一级需要降序排列,可以直接使用带 Descending 后缀的方法:

// 年龄升序 + 姓名降序
val sortedPeople = people.sortedWith(
    compareBy(Person::age).thenDescending(compareBy(Person::name))
)

assertIterableEquals(
    listOf(Person("Cleo", 29), Person("Alice", 29), Person("Bob", 31)),
    sortedPeople
)

或者进一步简化为:

val sortedPeople2 = people.sortedWith(
    compareByDescending(Person::age).thenByDescending(Person::name)
)

assertIterableEquals(
    listOf(Person("Bob", 31), Person("Cleo", 29), Person("Alice", 29)),
    sortedPeople2
)

✅ 多级降序也只需一行表达,非常直观。

4.5 thenComparator():自定义复合比较逻辑

当标准函数无法满足复杂比较需求时(比如比较字符串长度差、自定义评分等),可以使用 thenComparator() 写内联比较逻辑:

data class Person(val name: String, val age: Int)
val people = listOf(Person("Alice", 29), Person("Bob", 31), Person("Cleo", 29))

val combinedComparator = compareBy(Person::age)
    .thenComparator { p1, p2 -> p1.name.length - p2.name.length }

val sortedPeople = people.sortedWith(combinedComparator)

assertIterableEquals(
    listOf(Person("Cleo", 29), Person("Alice", 29), Person("Bob", 31)),
    sortedPeople
)

这里第二级排序依据是名字长度。由于没有现成的选择器可用,thenComparator() 提供了最大的灵活性。

5. 总结

Kotlin 的 Comparator 设计充分体现了函数式编程的优势:

  • compareBy 等顶层函数让简单排序一行搞定
  • then, thenBy 支持流畅的多字段排序
  • reversed, *Descending 轻松处理逆序场景
  • thenComparator 应对特殊业务逻辑无压力

📌 建议优先使用 compareBy().thenBy() 这种组合方式,既简洁又易维护。避免手写匿名类实现 Comparator,那不是 Kotlin 的风格。

合理利用这些工具,不仅能提升代码质量,还能显著减少 bug 出现的概率。尤其是在处理用户数据展示、报表排序等场景时,这套 API 真的是“越用越香”。🔥


原始标题:Comparator in Kotlin