1. 概述

Kotlin 在 Java 集合框架基础上,通过扩展函数增强了其可用性和可读性,无需依赖如 Apache CommonsGuava 这类第三方库。

本文将聚焦于 Kotlin 中的排序操作,并使用 kotlin.comparisons 包实现复杂的排序规则。

2. 集合排序

Kotlin 提供了多个实用函数来简化集合排序流程。下面我们逐一介绍。

2.1. sort()

最简单的排序方式是调用 sort() 方法。 它默认使用元素的自然顺序进行升序排序:

val sortedValues = mutableListOf(1, 2, 7, 6, 5, 6)
sortedValues.sort()
log.info("$sortedValues")

输出结果为:

[1, 2, 5, 6, 6, 7]

⚠️ 注意:我们使用的是可变集合(mutableListOf),因为 sort()原地排序(in-place),会直接修改原集合。

如果需要降序排序,可以使用 sortDescending() 或者 reverse()

2.2. sortBy()

当我们需要根据对象的某个属性排序时,可以使用 sortBy() 它接受一个选择器函数作为参数,该函数返回用于排序的值:

val sortedValues = mutableListOf(1 to "a", 2 to "b", 7 to "c", 6 to "d", 5 to "c", 6 to "e")
sortedValues.sortBy { it.second }
log.info("$sortedValues")

输出结果如下:

[(1, a), (2, b), (7, c), (5, c), (6, d), (6, e)]

⚠️ 同样,sortBy() 也是原地排序,所以必须使用可变集合。

如需降序排序,可使用 sortByDescending()reverse()

2.3. sortWith()

当需要实现多条件排序时,可以使用 sortWith() 它接受一个 Comparator 对象作为参数。我们将在下一节介绍如何创建它:

val sortedValues = mutableListOf(1 to "a", 2 to "b", 7 to "c", 6 to "d", 5 to "c", 6 to "e")
sortedValues.sortWith(compareBy({it.second}, {it.first}))
log.info("$sortedValues")

输出结果如下(先按字母排序,再按数字排序):

[(1, a), (2, b), (5, c), (7, c), (6, d), (6, e)]

⚠️ sortWith() 也是原地排序,因此必须使用可变集合。降序排序可以使用 reverse(),也可以自定义比较器。

3. 比较器(Comparator)

Kotlin 提供了 kotlin.comparisons 包来帮助我们构建 Comparator,包括:

  • 创建比较器
  • 处理 null 值
  • 反转排序
  • 扩展比较规则

3.1. 比较器创建

Kotlin 提供了多种工厂函数来创建比较器以提高可读性。

最简单的比较器是 naturalOrder(),它默认升序排序:

val ascComparator = naturalOrder<Long>()

对于包含多个属性的对象,可以使用 compareBy(),它接受多个排序规则:

val complexComparator = compareBy<Pair<Int, String?>>({it.first}, {it.second})

上面的例子中,先按 first 排序,若相同则按 second 排序。

3.2. 处理 null 值

为了处理 null 值,可以使用 nullsFirst()nullsLast() 函数。 它们分别将 null 值排在最前或最后:

val sortedValues = mutableListOf(1 to "a", 2 to null, 7 to "c", 6 to "d", 5 to "c", 6 to "e")
sortedValues.sortWith(nullsLast(compareBy { it.second }))
log.info("$sortedValues")

输出结果如下:

[(1, a), (7, c), (5, c), (6, d), (6, e), (2, null)]

可以看到,null 值被排在了最后。

3.3. 反转排序

可以通过 reverseOrder()reversed() 来反转排序。 前者返回一个默认的降序比较器,后者可以作用于任意比较器对象:

reverseOrder()

3.4. 扩展比较规则

可以使用 thenBy()thenByDescending() 等函数扩展比较器的排序规则。 只有当前比较器判断相等时,才会使用后续规则继续排序。

例如,先按年龄升序,再按姓名降序:

val students = mutableListOf(21 to "Helen", 21 to "Tom", 20 to "Jim") 

val ageComparator = compareBy<Pair<Int, String?>> {it.first}
val ageAndNameComparator = ageComparator.thenByDescending {it.second}
students.sortWith(ageAndNameComparator)
log.info("$students")

输出结果如下:

[(20, Jim), (21, Tom), (21, Helen)]

4. sort*()sorted*() 的区别

我们前面提到的 sort(), sortBy(), sortWith() 都是原地排序,而 sorted(), sortedBy(), sortedWith() 则是返回新列表。

sort*() 的特点:

  • MutableList 的扩展函数
  • 只适用于可变集合
  • 返回 Unit,即直接修改原集合(原地排序)
public actual fun <T : Comparable<T>> MutableList<T>.sort(): Unit {...}

sorted*() 的特点:

  • Iterable 的扩展函数
  • 适用于所有集合类型(包括不可变集合)
  • 返回一个新的 List不修改原集合
public fun <T : Comparable<T>> Iterable<T>.sorted(): List<T> {...}

总结

  • 如果是只读集合,只能使用 sorted*()
  • 如果是可变集合,可以选择:
    • 使用 sort*() 原地修改集合
    • 使用 sorted*() 创建新集合,保留原集合不变

5. 总结

本文介绍了 Kotlin 中常用的排序方法:

  • sort(), sortBy(), sortWith():适用于可变集合,原地排序
  • sorted(), sortedBy(), sortedWith():适用于任意集合,返回新列表

我们还使用 kotlin.comparisons 包创建了复杂的比较器,支持多条件排序、null 值处理和排序规则扩展。

所有示例代码和完整项目可以参考 GitHub 仓库


原始标题:Guide to Sorting in Kotlin