1. 概述

在 Kotlin 中,Enum(枚举)是一种非常实用的语言特性,它允许我们以类型安全的方式定义一组常量,并在整个应用中使用。然而,在实际开发中经常会遇到一个需求:需要遍历枚举中的所有项,比如用于数据校验、映射转换或生成选项列表等场景。

本文将系统介绍在 Kotlin 中如何遍历枚举项,涵盖不同版本下的推荐做法,并结合真实示例说明其用法和注意事项。✅

2. 问题引入

先来看一个典型的枚举示例:

enum class Weekday {
    MON, TUE, WED, THU, FRI
}

你可能会尝试直接对 Weekday 进行循环:

for (day in Weekday) { } // ❌ 编译失败

或者使用函数式风格:

Weekday.forEach { } // ❌ 同样无法编译

⚠️ 原因在于:枚举类本身并不是集合类型,不能直接被迭代。我们必须将其转换为可迭代的数据结构,例如 ArrayList

理想情况下,我们希望得到如下结果:

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


原始标题:Iterating Enum Entries in Kotlin