1. 概述
在 Kotlin 中,enum 是定义一组固定值的常用方式。它非常适合表示一组相关的常量,代码清晰且类型安全。
但在实际开发中,我们常常需要以编程方式获取某个枚举类中所有条目的名称(即 name
字段),例如用于日志打印、参数校验、前端下拉选项生成等场景。
本文将介绍几种使用泛型函数实现该需求的方法,重点对比其适用性与优缺点,帮你避开一些常见的“坑”。
2. 问题背景
为了支持任意枚举类型,我们需要一个通用的、可复用的函数,能够接受任意 Enum<T>
类型并返回其所有条目的名称列表。
先定义两个示例枚举类,便于后续验证:
enum class CountryCode(countryName: String) {
USA("United States of America"),
UKR("Ukraine"),
CAN("Canada"),
MEX("Mexico"),
JAM("Jamaica")
}
enum class WorkingDay {
Monday, Tuesday, Wednesday, Thursday, Friday
}
✅ 注意:CountryCode
带有构造参数和属性,而 WorkingDay
是最简单的枚举形式。我们的目标是写出一个能同时处理这两种情况的泛型函数。
最终期望效果如下:
getNames<WorkingDay>() // 返回 ["Monday", "Tuesday", ..., "Friday"]
getNames<CountryCode>() // 返回 ["USA", "UKR", "CAN", "MEX", "JAM"]
下面我们来看几种主流实现方式。
3. 使用 Java 的 Class.getEnumConstants()
方法
Java 的 java.lang.Class
提供了 getEnumConstants()
方法,可以返回枚举类的所有实例数组。
在 Kotlin 中也可以直接调用这个方法,步骤如下:
- 获取枚举类的 Java
Class
对象(通过::class.java
) - 调用
enumConstants
属性获取实例数组 - 使用
map { it.name }
提取名称
示例代码:
val names = WorkingDay::class.java.enumConstants.map { it.name }
assertEquals(listOf("Monday", "Tuesday", "Wednesday", "Thursday", "Friday"), names)
将其封装为泛型函数:
fun getNames1(enumCls: Class<out Enum<*>>) = enumCls.enumConstants.map(Enum<*>::name)
⚠️ 注意点:
- 参数必须是
Class<out Enum<*>>
,确保传入的是枚举类的 Class 对象 - 调用时需显式传递
::class.java
,语法略显冗长
测试验证:
val dayNames = getNames1(WorkingDay::class.java)
assertEquals(listOf("Monday", "Tuesday", "Wednesday", "Thursday", "Friday"), dayNames)
val codeNames = getNames1(CountryCode::class.java)
assertEquals(listOf("USA", "UKR", "CAN", "MEX", "JAM"), codeNames)
✅ 优点:兼容性强,适用于所有 Kotlin 版本
❌ 缺点:调用不够简洁,依赖 Java API,风格不够“Kotlin”
4. 使用 Kotlin 的 enumValues<T>()
函数
Kotlin 标准库提供了 enumValues<T>()
函数,专门用于获取指定枚举类型的所有条目:
inline fun <reified T : Enum<T>> enumValues(): Array<T>
关键特性:
- ✅
reified
类型参数允许运行时获取泛型信息 - ✅
inline
函数避免额外开销 - ✅ 纯 Kotlin 实现,无需依赖 Java Class API
基于此,我们可以写出更简洁的泛型函数:
inline fun <reified T : Enum<T>> getNames2() = enumValues<T>().map { it.name }
调用方式非常直观:
val dayNames = getNames2<WorkingDay>()
assertEquals(listOf("Monday", "Tuesday", "Wednesday", "Thursday", "Friday"), dayNames)
val codeNames = getNames2<CountryCode>()
assertEquals(listOf("USA", "UKR", "CAN", "MEX", "JAM"), codeNames)
✅ 优点:
- 语法简洁,符合 Kotlin 风格
- 性能良好(内联 + reified)
- 不依赖 Java API
❌ 缺点:
- 必须使用
<T>
显式指定类型,不能自动推导 - 在某些高阶用法中可能受限于类型擦除(但
reified
已解决大部分问题)
📌 推荐指数:⭐⭐⭐⭐☆(适合大多数场景)
5. 使用 values()
函数和 entries
属性
虽然前面两种方式都能工作,但调用形式仍是“函数式”的,比如 getNames2<WorkingDay>()
。而在实际编码中,我们更希望写成类似 WorkingDay.values().names()
这样的链式调用,更具可读性和流畅性。
5.1 扩展 values()
数组 —— 添加 .names()
扩展函数
Kotlin 允许为数组添加扩展函数。我们可以为所有 Array<out Enum<T>>
类型添加一个 names()
扩展:
fun <T : Enum<T>> Array<T>.names() = this.map { it.name }
这样就可以像下面这样调用:
val dayNames = WorkingDay.values().names()
assertEquals(listOf("Monday", "Tuesday", "Wednesday", "Thursday", "Friday"), dayNames)
val codeNames = CountryCode.values().names()
assertEquals(listOf("USA", "UKR", "CAN", "MEX", "JAM"), codeNames)
✅ 优点:
- 调用极其自然,符合 Kotlin DSL 风格
- 复用现有
values()
方法,无额外性能损耗 - 可与其他集合操作无缝衔接(如过滤、排序等)
⚠️ 注意:values()
每次调用都会创建新数组(除非 JVM 优化),频繁调用可能影响性能。
5.2 使用 entries
属性(Kotlin 1.9+ 推荐)
从 Kotlin 1.8.20 开始,JetBrains 引入了实验性的 entries
属性作为 values()
的现代替代方案。自 Kotlin 1.9.0 起已稳定可用。
主要优势:
- ❌
entries
返回的是只读List
,而非每次新建的数组 - ✅ 更节省内存,更适合高频访问场景
- ✅ 支持序列化友好访问(尤其与 kotlinx.serialization 配合更好)
我们可以为 EnumEntries<T>
添加类似的扩展:
fun <T : Enum<T>> EnumEntries<T>.names() = this.map { it.name }
使用方式几乎一致:
val dayNames = WorkingDay.entries.names()
assertEquals(listOf("Monday", "Tuesday", "Wednesday", "Thursday", "Friday"), dayNames)
val codeNames = CountryCode.entries.names()
assertEquals(listOf("USA", "UKR", "CAN", "MEX", "JAM"), codeNames)
✅ 强烈建议升级到 Kotlin 1.9+ 的项目优先使用 entries
替代 values()
。
📌 对比总结:
方式 | 返回类型 | 是否每次新建 | 推荐程度 |
---|---|---|---|
values() |
Array<T> |
是 | ⭐⭐⭐ |
entries |
List<T> (共享) |
否 | ⭐⭐⭐⭐⭐ |
6. 结论
本文系统介绍了四种获取枚举条目名称的泛型方法,各有适用场景:
方法 | 适用版本 | 推荐度 | 说明 |
---|---|---|---|
Class.getEnumConstants() |
所有版本 | ⭐⭐ | 兼容老项目,但偏 Java 风格 |
enumValues<T>() |
所有版本 | ⭐⭐⭐⭐ | Kotlin 原生推荐,简洁高效 |
values().names() 扩展 |
所有版本 | ⭐⭐⭐⭐ | 流畅调用,适合 DSL 风格 |
entries.names() 扩展 |
Kotlin 1.9+ | ✅ 首选 | 最佳实践,性能与语义俱佳 |
📌 最终建议:
- 若使用 Kotlin ≥ 1.9,优先采用
entries
+ 扩展函数的方式 - 否则使用
enumValues<T>()
封装即可 - 避免在高频路径中反复调用
values()
,防止不必要的对象创建
所有示例代码均可在 GitHub 查看:
https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-lang-oop-3