跳到主要内容

Kotlin 2.4.20 集合新 API 实战:用 allDistinct 和 allEqual 告别手动循环判重

· 阅读需 4 分钟
重庆 Kotlin User Group

在日常开发中,你一定写过这样的逻辑:判断一个列表里的元素是否全部相同,或者检查某个字段是否存在重复。过去我们要么手写 for 循环,要么借助 toSet().size == size 这种"凑合能用"的写法。Kotlin 2.4.20 在标准库中正式引入了 allDistinct()、allEqual() 以及它们的 By 变体,把这些常见需求提升为一等公民 API。

痛点:那些我们写过的"笨办法"​

先看几个典型场景。

场景一:检查集合元素是否全部相同

// 以前的写法
val allSame = responses.map { it.status }.toSet().size == 1
// 或者
val allSame = responses.all { it.status == responses.first().status }

第一种写法会创建中间 Set,分配内存;第二种在空集合上会抛异常。两种都不够优雅。

场景二:检查是否存在重复元素

// 以前的写法
val hasDuplicates = ids.toSet().size != ids.size

简洁但低效——你其实只需要发现第一个重复就可以短路返回,而 toSet() 会遍历整个集合。

场景三:按某个属性检查唯一性

// 以前的写法
val allUniqueNames = users.map { it.name }.toSet().size == users.size

又一次中间集合分配,又一次全量遍历。

新 API 一览​

Kotlin 2.4.20 新增了四个实验性函数,均位于 kotlin.collections 包下:

函数作用
allDistinct()集合中所有元素是否互不相同
allDistinctBy { }按指定选择器,所有元素的派生值是否互不相同
allEqual()集合中所有元素是否全部相同
allEqualBy { }按指定选择器,所有元素的派生值是否全部相同

这些函数目前标记为 @ExperimentalStdlibApi,需要 opt-in 才能使用。

实战演示​

@OptIn(ExperimentalStdlibApi::class)
fun main() {
data class Response(
val participantId: String,
val answer: String,
val responseDate: String
)

val responses = listOf(
Response("P001", "Yes", "2026-07-21"),
Response("P002", "Maybe", "2026-07-21"),
Response("P003", "No", "2026-07-21")
)

// 所有参与者是否给出了相同答案?
println(responses.allEqualBy { it.answer }) // false

// 参与者 ID 是否全部唯一?(无重复提交)
println(responses.allDistinctBy { it.participantId }) // true

// 所有回答是否在同一天提交?
println(responses.allEqualBy { it.responseDate }) // true

val answers = responses.map { it.answer }

// 答案列表是否全部相同?
println(answers.allEqual()) // false

// 答案列表是否互不相同?
println(answers.allDistinct()) // true
}

为什么不用 toSet().size?​

你可能觉得 toSet().size == size 已经够用了。新 API 的价值在于三点:

1. 语义清晰

allDistinct() 直接表达了"所有元素互不相同"的意图,而 toSet().size == size 需要读者在脑中做一次转换——"转成 Set 后大小没变,说明没有重复"。声明式 API 让代码即文档。

2. 短路优化

allDistinct() 和 allEqual() 的实现可以在发现第一个违反条件的元素时立即返回 false,无需遍历整个集合。对于大集合或早期就能判断出结果的场景,性能差异显著。而 toSet() 无论如何都要完整遍历并分配内存。

3. 零分配

toSet() 会创建一个完整的 HashSet 副本。allDistinctBy { } 相比 map { }.toSet().size 省去了中间 List 和 Set 两次分配。在处理大量数据或对 GC 敏感的场景中,这一点很有价值。

更多实用场景​

表单验证:检查输入是否全部一致​

@OptIn(ExperimentalStdlibApi::class)
fun validatePoll(poll: Poll): List<String> {
val errors = mutableListOf<String>()

if (poll.options.allEqualBy { it.text }) {
errors.add("所有选项内容相同,请修改")
}

if (!poll.options.allDistinctBy { it.text }) {
errors.add("存在重复选项")
}

return errors
}

数据校验:确保 ID 唯一性​

@OptIn(ExperimentalStdlibApi::class)
fun <T> validateUniqueIds(items: List<T>, idSelector: (T) -> Any?): Boolean {
return items.allDistinctBy(idSelector)
}

// 使用
val valid = validateUniqueIds(users) { it.email }

配置检查:确认环境一致性​

@OptIn(ExperimentalStdlibApi::class)
fun checkClusterConsistency(nodes: List<Node>): Boolean {
// 所有节点是否运行相同版本?
return nodes.allEqualBy { it.version }
}

如何启用​

由于这些函数目前仍处于实验阶段,你需要在代码或构建配置中 opt-in:

文件级别:

@file:OptIn(ExperimentalStdlibApi::class)

函数级别:

@OptIn(ExperimentalStdlibApi::class)
fun checkDistinct(items: List<Any>) = items.allDistinct()

Gradle 全局配置:

kotlin {
compilerOptions {
optIn.addAll("kotlin.ExperimentalStdlibApi")
}
}

小结​

Kotlin 2.4.20 的 allDistinct、allEqual 系列函数虽然看似"小功能",却精准命中了日常开发中的高频痛点。它们用声明式 API 替代了命令式的 toSet().size 技巧,同时带来了短路优化和零分配的实质收益。如果你的项目已经升级到 Kotlin 2.4.20+,不妨在下次遇到集合判重或一致性检查时,试试这些新 API。