1. 简介
在 Kotlin 中,枚举(Enum) 是一种强大的语言特性,允许我们定义一组命名常量。它常用于表示封闭的值集合,比如一周中的每一天或一年中的各月份。实际开发中,我们经常会遇到一个需求:判断某个字符串是否对应某个 enum 的值。
本文将介绍几种在 Kotlin 中判断 enum 是否包含指定字符串的方法,帮助你在不同场景下选择最优解,避免踩坑 ❌。
2. Enum 类定义
为了统一演示,我们先定义一个表示“星期”的枚举类:
enum class DaysOfWeek {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
companion object {
val names by lazy { DaysOfWeek.values().map { it.name } }
}
}
这个 DaysOfWeek
枚举包含了七天。同时,在其伴生对象(companion object)中,我们通过 lazy
延迟初始化了一个 names
列表,保存所有枚举项的名称(即 name
字段)。这能提升性能,尤其在频繁调用但初始化成本较高的场景下 ✅。
3. 使用 for 循环
最直观的方式是遍历所有枚举值,逐一比对名称:
fun containsValueUsingForLoop(value: String): Boolean {
for (day in DaysOfWeek.values()) {
if (day.name == value) {
return true
}
}
return false
}
✅ 优点:逻辑清晰,兼容性好,适合初学者理解。
❌ 缺点:手动写循环略显冗余,不够函数式。
单元测试验证
@Test
fun `checks if enum value contains a specific string correctly using for loop`() {
assertFalse(containsValueUsingForLoop("RED"))
assertTrue(containsValueUsingForLoop("WEDNESDAY"))
assertFalse(containsValueUsingForLoop("AUGUST"))
assertTrue(containsValueUsingForLoop("SATURDAY"))
}
测试覆盖了命中与未命中的情况,确保逻辑正确。
4. 使用 when 表达式
Kotlin 的 when
不仅是 switch 的升级版,还能结合 in
操作符实现成员判断:
fun containsValueUsingWhenExpression(value: String): Boolean {
return when (value) {
in DaysOfWeek.names -> true
else -> false
}
}
⚠️ 注意:这里依赖了前面定义的 names
属性,本质上是判断字符串是否在名称列表中。
✅ 优点:语法简洁,语义明确,无需显式循环。
❌ 缺点:仍需维护 names
列表,若忘记更新可能出错。
测试代码
@Test
fun `checks if enum value contains a specific string correctly using when expression`() {
assertFalse(containsValueUsingWhenExpression("RED"))
assertTrue(containsValueUsingWhenExpression("WEDNESDAY"))
assertFalse(containsValueUsingWhenExpression("AUGUST"))
assertTrue(containsValueUsingWhenExpression("SATURDAY"))
}
结果符合预期。
5. 使用 map 方法
我们可以利用 map
提取所有枚举名称,生成一个字符串列表,再使用 contains
判断:
@Test
fun `checks if enum value contains a specific string correctly using map method`() {
val names = DaysOfWeek.values().map { it.name }
assertFalse(names.contains("RED"))
assertTrue(names.contains("WEDNESDAY"))
assertFalse(names.contains("AUGUST"))
assertTrue(names.contains("SATURDAY"))
}
⚠️ 虽然可行,但这种方式会完整遍历所有枚举值并创建新列表,即使第一个就匹配成功也不会提前退出 —— 存在性能浪费。
5.1 使用 any 方法(推荐优化)
更高效的做法是使用 any
,它支持短路求值(short-circuiting),一旦找到匹配项就立即返回 true
:
@Test
fun `checks if enum value contains a specific string correctly using any method`() {
val values = DaysOfWeek.values()
assertFalse(values.any { it.name == "RED" })
assertTrue(values.any { it.name == "WEDNESDAY" })
assertFalse(values.any { it.name == "AUGUST" })
assertTrue(values.any { it.name == "SATURDAY" })
}
✅ 核心优势:
- 不需要预先构建列表
- 遇到第一个匹配项即停止迭代
- 内存友好,时间复杂度更优(平均情况下)
📌 实际项目中建议优先使用 any
替代 map + contains
,特别是在枚举项较多时性能差异明显。
6. 使用 HashSet
另一种思路是将所有枚举名称预加载进 HashSet
,利用哈希表 O(1) 查找特性提升查询效率:
fun containsValueUsingHashSet(value: String): Boolean {
val set = HashSet<String>()
DaysOfWeek.values().forEach { set.add(it.name) }
return set.contains(value)
}
不过上面写法每次调用都重建 HashSet
,显然不合理 ❌。生产环境应改为静态缓存:
enum class DaysOfWeek {
// ... 其他枚举值
;
companion object {
private val nameSet by lazy {
hashSetOf<String>().apply {
values().forEach { add(it.name) }
}
}
fun containsName(name: String): Boolean = nameSet.contains(name)
}
}
✅ 适用场景:高频查询、低频变更的枚举(如配置类型、状态码等)
⚠️ 注意点:务必配合 lazy
或 static
缓存,否则得不偿失。
测试验证
@Test
fun `checks if enum value contains a specific string correctly using a hashset`() {
assertFalse(containsValueUsingHashSet("RED"))
assertTrue(containsValueUsingHashSet("WEDNESDAY"))
assertFalse(containsValueUsingHashSet("AUGUST"))
assertTrue(containsValueUsingHashSet("SATURDAY"))
}
7. 总结
方法 | 是否推荐 | 说明 |
---|---|---|
for 循环 |
⚠️ 一般 | 易懂但不够优雅 |
when + in names |
✅ 可用 | 依赖辅助属性,注意一致性 |
map { name } + contains |
❌ 不推荐 | 全量映射,无法短路 |
any { name == value } |
✅✅ 强烈推荐 | 短路求值,零额外内存开销 |
HashSet 缓存 |
✅ 高频查询场景 | 查询快,适合常驻内存 |
📌 最佳实践建议:
- 日常小枚举 ➜ 直接用
any
- 大枚举或高频查询 ➜ 配合
HashSet
+lazy
缓存 - 避免重复造轮子,善用标准库函数
掌握这些技巧不仅能写出更高效的代码,也能加深对 Kotlin 函数式编程特性的理解。下次遇到类似需求时,就知道该怎么选了 💡。