1. 概述
在 Kotlin 开发中,我们经常需要判断一个数组是否包含某个特定值。这种操作看似简单,但在实际编码中若不注意性能或惯用写法,容易写出不够优雅甚至低效的代码。
本文将系统性地探讨在 Kotlin 中如何高效、简洁地实现「检查数组是否包含某值」以及「是否包含多个候选值中的任意一个」这两种常见场景,并给出推荐的最佳实践。
2. 问题分析
这个问题可以细分为两种典型场景:
✅ 单值匹配:判断数组中是否存在某个具体元素
✅ 多值任一匹配:给定一组候选值,判断数组中是否至少包含其中一个
我们将分别讨论每种情况的多种实现方式,并指出各自的优劣和适用场景。
为便于演示,先定义一个示例数组:
val myArray = arrayOf("Ruby", "Java", "Kotlin", "Go", "Python", "Perl")
💡
arrayOf()
是 Kotlin 中初始化数组的惯用方式,简洁直观。
接下来我们逐一解决上述两类问题。
3. 检查数组是否包含单个指定值
3.1 使用 any
函数
Kotlin 的 Array
类扩展了 any
高阶函数,它接受一个谓词(predicate),只要有一个元素满足条件就返回 true
。
assertThat(myArray.any { it == "Kotlin" }).isTrue
assertThat(myArray.any { it == "Php" }).isFalse
这种方式灵活且支持复杂条件判断,但用于简单的相等比较时略显啰嗦。
⚠️ 注意:any
会在找到第一个匹配项后立即终止遍历,具备短路特性,性能良好。
3.2 使用 in
操作符(推荐)
Kotlin 提供了更符合语言习惯的写法 —— in
操作符。它底层调用的是 contains
方法,语义清晰、代码简洁。
assertThat("Kotlin" in myArray).isTrue
assertThat("Php" in myArray).isFalse
✅ 结论:对于单值查找,value in array
是最 idiomatic(地道)的写法,应优先使用。
踩坑提醒:有些人误以为
in
只能用于循环,其实它本质是可重载的操作符,在集合查找中非常自然。
4. 检查数组是否包含多个候选值中的任意一个
现在考虑更复杂的场景:给定一组值,判断原数组是否至少包含其中某一个。
val toBeChecked1 = setOf("Scala", "Php", "whatever", "Kotlin")
val toBeChecked2 = setOf("Scala", "Php", "Javascript", "whatever")
目标是判断 myArray
是否与这些集合有交集。
4.1 借助 Java 的 Collections.disjoint
方法
思路是将两个集合转为 Set
,然后利用 Collections.disjoint()
判断它们是否无交集。如果 不 disjoint,说明存在共同元素。
val mySet = myArray.toSet()
assertThat(!Collections.disjoint(mySet, toBeChecked1)).isTrue
assertThat(!Collections.disjoint(mySet, toBeChecked2)).isFalse
✅ 优点:逻辑清晰,来自 JDK,稳定可靠
❌ 缺点:需引入 Java 工具类,不够“纯 Kotlin”
disjoint
意为“互不相交”,所以取反才是“有交集”。
4.2 使用 Kotlin 的 intersect
函数
Kotlin 标准库提供了 intersect
扩展函数,返回两个集合的交集。
assertThat((toBeChecked1 intersect mySet).isNotEmpty()).isTrue
assertThat((toBeChecked2 intersect mySet).isNotEmpty()).isFalse
✅ 优点:
- 纯 Kotlin 实现
infix
写法(如a intersect b
)读起来像自然语言
⚠️ 注意:intersect
会计算完整交集,即使只需知道是否存在即可,因此在大数据量下可能不如短路方案高效。
4.3 结合 any
和 in
操作符(推荐)
这是最高效且常用的组合拳:
assertThat(myArray.any { it in toBeChecked1 }).isTrue
assertThat(myArray.any { it in toBeChecked2 }).isFalse
✅ 优势:
- 短路执行:一旦发现匹配即返回,无需遍历全部
- 语法简洁,语义明确
- 不创建中间集合,内存友好
你也可以反过来写:
assertThat(toBeChecked1.any { it in myArray }).isTrue
但通常建议让大集合调用 any
,小集合作为 in
的右操作数,这样缓存命中率更高。
进阶技巧:方法引用
还可以进一步简化为方法引用形式:
assertThat(myArray.any(toBeChecked1::contains)).isTrue
assertThat(myArray.any(toBeChecked2::contains)).isFalse
这等价于 { it -> toBeChecked1.contains(it) }
,更加函数式。
4.4 自定义 infix
扩展函数(高级用法)
为了让代码更具表达力,我们可以封装一个 DSL 风格的扩展函数:
infix fun <T> Array<T>.containsAnyFrom(elements: Collection<T>): Boolean {
return any(elements.toSet()::contains)
}
使用方式如下:
assertThat(myArray containsAnyFrom toBeChecked1).isTrue
assertThat(myArray containsAnyFrom toBeChecked2).isFalse
✅ 优点:
- API 极其清晰,接近自然语言
- 可复用,适合团队内部统一风格
⚠️ 注意:仅当项目中频繁出现此类逻辑时才建议封装,避免过度设计。
5. 总结
场景 | 推荐方案 | 说明 |
---|---|---|
✅ 单值查找 | "value" in array |
最简洁、最 idiomatic |
✅ 多值任一匹配 | array.any { it in candidates } |
短路高效,推荐首选 |
⚠️ 多值匹配(次选) | intersect().isNotEmpty() |
适合需要交集结果的场景 |
🔧 团队项目 | 自定义 infix 扩展 |
提升代码可读性 |
最终选择哪种方式,取决于你的具体需求:是追求极致性能、代码简洁,还是更高的可读性。
完整示例代码已托管至 GitHub:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-arrays