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 结合 anyin 操作符(推荐)

这是最高效且常用的组合拳:

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


原始标题:Check if an Array Contains a Given Value in Kotlin