1. 概述
在 Kotlin 中,Enum(枚举)是一种非常实用的语言特性,它允许我们以类型安全的方式定义一组常量,并在整个应用中使用。然而,在实际开发中经常会遇到一个需求:需要遍历枚举中的所有项,比如用于数据校验、映射转换或生成选项列表等场景。
本文将系统介绍在 Kotlin 中如何遍历枚举项,涵盖不同版本下的推荐做法,并结合真实示例说明其用法和注意事项。✅
2. 问题引入
先来看一个典型的枚举示例:
enum class Weekday {
MON, TUE, WED, THU, FRI
}
你可能会尝试直接对 Weekday
进行循环:
for (day in Weekday) { } // ❌ 编译失败
或者使用函数式风格:
Weekday.forEach { } // ❌ 同样无法编译
⚠️ 原因在于:枚举类本身并不是集合类型,不能直接被迭代。我们必须将其转换为可迭代的数据结构,例如 Array
或 List
。
理想情况下,我们希望得到如下结果:
val expectedWeekdayArray = arrayOf(MON, TUE, WED, THU, FRI)
val expectedWeekdayList = listOf(MON, TUE, WED, THU, FRI)
一旦有了数组或列表,后续的遍历就毫无障碍了:
expectedWeekdayArray.forEach { /* ✅ 正常编译 */ }
for (day in expectedWeekdayList) { /* ✅ 正常编译 */ }
接下来我们就来看看,如何从一个枚举获取包含所有实例的数组或列表。
3. Kotlin 1.9 之前:使用 values()
函数
在 Kotlin 所有枚举类型中,都自带一个静态方法 values()
,它的作用是:
✅ 返回该枚举中所有常量组成的 数组(Array)
✅ 数组中元素的顺序与枚举声明时一致
使用方式如下:
val result = Weekday.values()
assertArrayEquals(expectedWeekdayArray, result)
拿到这个数组后,就可以自由地进行遍历、过滤、映射等操作。
⚠️ 注意:
values()
是编译器自动生成的静态方法,底层基于 JVM 的枚举机制实现。每次调用都会返回一个新的数组副本 —— 虽然通常影响不大,但在高频调用场景下可能成为性能隐患(后文会提到)。
不过从 Kotlin 1.9 开始,官方推出了更优替代方案 —— entries
属性。
4. Kotlin 1.9 及以上:使用 entries
属性 ✅ 推荐
JetBrains 团队在 GitHub issue #1372 中指出,频繁调用 values()
会导致不必要的对象创建和内存开销,尤其是在序列化库中表现明显。
为此,自 Kotlin 1.9 起引入了 entries
属性作为标准实践:
public sealed interface EnumEntries<E : Enum<E>> : List<E>
关键点:
entries
是一个只读属性- 类型为
EnumEntries<E>
,继承自List<E>
- 因此可以直接当作
List
使用,支持forEach
,map
,filter
等操作
示例:
val result = Weekday.entries
assertEquals(expectedWeekdayList, result)
遍历方式也变得非常自然:
Weekday.entries.forEach { println(it) }
val workdays = Weekday.entries.filter { it != Weekday.SAT && it != Weekday.SUN }
✅ **建议新项目统一使用 entries
**,特别是在 Kotlin 1.9+ 环境下。它不仅语义更清晰,而且避免了 values()
的性能问题。
5. 使用 enumValues<T>()
函数:泛型友好方案
除了上述两种方式,Kotlin 还提供了一个顶层内联函数:
public inline fun <reified T : Enum<T>> enumValues(): Array<T>
该函数通过 reified 类型参数,返回指定枚举类型的全部实例数组。
使用示例:
val weekdayResult = enumValues<Weekday>()
assertArrayEquals(expectedWeekdayArray, weekdayResult)
再看另一个枚举:
enum class Holiday {
Eastern, Christmas, LaborDay, NewYear
}
同样可以轻松获取其实例数组:
val expectedHolidayArray = arrayOf(Eastern, Christmas, LaborDay, NewYear)
val holidayResult = enumValues<Holiday>()
assertArrayEquals(expectedHolidayArray, holidayResult)
📌 优势总结:
- 支持泛型编程
- 在运行时通过反射获取枚举值(得益于
reified
) - 特别适合编写通用工具函数
虽然 entries
更高效,但 enumValues()
在泛型上下文中更具灵活性。
6. 实战案例:在泛型函数中使用 enumValues()
设想这样一个需求:
我们要实现一个通用函数,根据字符串名称查找对应的枚举实例,要求:
- 忽略大小写和空格
- 查不到时返回 null
- ✅ 支持任意枚举类型
显然这是一个典型的泛型场景。我们可能会尝试这样写:
inline fun <reified E : Enum<E>> findEnumByName(name: String): E? {
return E.entries.firstOrNull {
it.name.equals(name.replace(" ", ""), ignoreCase = true)
}
}
但这段代码 ❌ 无法编译,报错如下:
Kotlin: Type parameter 'E' cannot have or inherit a companion object,
so it cannot be on the left hand side of dot
问题出在 E.entries
—— 泛型类型 E
无法直接访问 .entries
,因为编译器不知道它是否有这个属性。
✅ 正确解法是借助 enumValues()
:
inline fun <reified E : Enum<E>> findEnumByName(name: String): E? {
return enumValues<E>().firstOrNull {
it.name.equals(name.replace(" ", ""), ignoreCase = true)
}
}
测试验证:
val tuesday = findEnumByName<Weekday>("t U e")
assertEquals(TUE, tuesday)
val newYear = findEnumByName<Holiday>("N E W Y E A R")
assertEquals(NewYear, newYear)
val unknown = findEnumByName<Holiday>("Thanksgiving")
assertNull(unknown)
✅ 成功支持多枚举类型,且逻辑简洁可靠。
💡 小贴士:这种模式常用于 REST 接口参数解析、配置项映射等场景,属于“踩坑”后才会意识到
enumValues()
不可或缺。
7. 总结
方法 | 适用版本 | 返回类型 | 是否推荐 | 场景 |
---|---|---|---|---|
values() |
所有版本 | Array<T> |
❌ 不推荐 | 旧代码兼容 |
entries |
Kotlin 1.9+ | List<T> |
✅ 强烈推荐 | 日常遍历 |
enumValues<T>() |
所有版本 | Array<T> |
✅ 推荐 | 泛型函数 |
📌 核心结论:
- 新项目优先使用
Weekday.entries
遍历枚举 ✅ - 若涉及泛型处理,选用
enumValues<T>()
更灵活 ✅ - 避免在热点路径频繁调用
values()
,防止潜在性能问题 ⚠️
掌握这三种方式,能让你在处理枚举时游刃有余,无论是做数据绑定、校验还是 DSL 设计都能得心应手。
完整示例代码已托管至 GitHub:https://github.com/baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-lang-oop-4