枚举类
枚举类表示一组固定的可能值. 当一个值只能是预先定义的几个选项之一时, 例如可用的状态, 或模式, 这种情况下请使用枚举类.
枚举类中的每个值称为 枚举常数. 枚举常数的行为类似于枚举类类型的 单例对象(Singleton), 因此可以拥有属性, 函数, 以及自定义的行为.
如果所有可能的值都事先已知, 并且具有相同的结构, 枚举类最为适合. 如果需要对各种情况保存不同的数据, 或采用不同的结构, 请使用 封闭类(Sealed Class)与封闭接口(Sealed Interface).
声明枚举类
要创建枚举类, 请使用 enum 关键字, 然后按通常的类语法, 在花括号中定义类体. 在类的 Body 部之内, 列出枚举常数, 使用逗号分隔:
在这个示例中, Direction 是枚举类, NORTH, SOUTH, WEST 和 EAST 是枚举常数.
按照惯例, 枚举常数通常使用大写字母, 因为它们表示常数值.
要访问枚举常数, 可以使用枚举类名称加上常数名称:
Kotlin 中的所有枚举类都继承自基类 Enum<T>, 其中 T 是枚举类自身. 例如, Direction 枚举类继承自 Enum<Direction>. 因此, 枚举常数具有内建的属性, 例如 name 和 ordinal.
使用枚举常数
枚举常数是值, 因此可以将它们赋值给变量, 打印输出, 传递给函数, 比较, 也可以在 when 表达式中使用.
声明枚举常数
要声明枚举常数, 先在枚举类的构造器中定义属性, 然后在每个枚举常数后的括号内传入属性值. 与某些其他语言不同, Kotlin 不使用赋值语法, 例如 RED = "#FF0000".
枚举常数可以关联任何类型的值. 常见的情况是字符串和数字, 也可以使用其它类型, 例如 Boolean, 另一个枚举类, 或自定义的类.
例如, Color 枚举类为每种颜色保存 16 进制的颜色代码:
传递给每个枚举常数的值必须与构造器参数的类型匹配. 这里, hex 是 Color 枚举类的字符串属性. 每个枚举常数都为这个属性传入自己的字符串值.
也可以为枚举常数关联数值. 例如, 在构造器中声明 Int 类型, 并为每个枚举常数提供 Int 值:
访问枚举常数和它的属性
可以通过枚举类的名称来访问枚举常数. 要访问与枚举常数关联的属性, 请使用点号表示法, 例如 color.hex 或 Color.GREEN.hex:
这里, Color.RED 是 Color 类型的枚举常数. color 变量保存这个枚举常数.
除了你自己定义的属性, 每个枚举常数还有内建的 name 和 ordinal 属性, 用于获取它在枚举类声明中的名称和位置(位置从 0 开始):
将枚举常数传递给函数
枚举常数是值, 因此可以传递给函数. 通过这种方式, 函数能够只接受枚举类中定义的固定的选项, 保证代码的类型安全:
这里, printColor() 函数接收 Color 类型的值, 因此可以传入任何 Color 枚举常数.
虽然枚举常数的行为类似于单例对象, 但编译器将它们视为枚举类类型的值. 可以将枚举类的名称用作类型, 但不能将枚举常数用作枚举类型:
在 when 表达式中使用枚举常数
如果需要分别处理每个枚举常数, 枚举类很适合与 when 表达式配合使用:
当 when 表达式涵盖了所有的枚举常数时, 不需要 else 分支.
查找枚举常数
有时需要根据字符串, 索引, 或关联的值来查找枚举常数. Kotlin 提供了内建的 API, 可以根据名称, 位置, 或自定义值查找常数.
例如, 假设枚举类为每种颜色关联了 RGB 值. 要根据名称查找枚举常数, 请使用 valueOf() 函数:
传给 valueOf() 的名称必须与枚举常数名称完全一致. 如果不存在指定名称的枚举常数, valueOf() 会抛出 IllegalArgumentException 异常.
要根据枚举声明中的位置查找枚举常数, 请在枚举类的 entries 属性上使用 getOrNull() 函数:
枚举常数的位置从 0 开始. 在这个示例中, RED 的位置是 0, GREEN 是 1, BLUE 是 2.
当一个整数表示枚举常数的位置时, 例如来自文件, 或用户的输入, 这种方式会很有用. 与某些其他语言不同, Kotlin 不允许将 Int 直接转换为枚举常数. 请将整数作为下标, 使用 entries.getOrNull(index) 查找常数.
如果这个整数表达的值, 即使在你重新排列枚举常数的顺序之后, 也应该保持稳定, 请定义明确的数值属性, 例如 rgb 或 code, 然后查找具有匹配的属性值的常数.
entries 是一种专门的 List, 因此可以对它使用标准的集合 API. 例如, 要根据关联的值查找枚举常数, 可以在枚举条目中使用 first() 进行搜索:
如果找不到匹配的常数, first() 会抛出 NoSuchElementException 异常. 如果希望得到 null, 请使用 firstOrNull().
要获取枚举常数的数量, 请使用 size 属性. 例如:
如果经常需要根据名称, 位置, 或关联的值查找枚举常数, 可以在 同伴对象 中添加帮助函数:
如果希望安全查找, 并在找不到时返回 null, 而不是抛出异常, 伴生对象中的辅助函数会很有用.
上述 entries 和 valueOf() 等查找 API 是 合成(synthetic) 成员的范例. 在这里, 合成表示 Kotlin 会自动提供这些成员, 即使你没有自己声明它们. 因此, 每个枚举类都能通过 entries 属性列出它的常数, 并通过 valueOf() 函数按名称查找常数, 不需要编写额外的代码.
也可以使用泛型帮助函数访问枚举类的常数, 例如 enumEntries<T>() 和 enumValueOf<T>(). 这些函数使用 实体化的类型参数(Reified Type Parameter). 这种参数使实际的枚举类型在泛型内联函数中仍然可用, 因此帮助函数可以直接使用枚举类型 T:
函数 | 说明 |
|---|---|
(推荐使用) 返回枚举类型 | |
返回包含枚举类型 | |
根据名称返回一个枚举条目. 如果没有匹配的条目, 会抛出 |
例如:
关于内联函数和实体化的类型参数, 详情请参见 内联函数.
枚举常数的比较和排序
请使用 == 结构相等 操作符, 比较枚举常数:
每个枚举常数的行为类似于单例对象, 因此比较枚举常数会检查两个值是否指向同一个常数.
所有枚举类默认实现 Comparable 接口, 因此可以比较和排序枚举常数. 常数的顺序由它们在枚举声明中的位置 (ordinal 值) 决定, 因此, 最先声明的常数被视为最小:
排序会遵循声明的相同顺序. 例如, 无论常数的名称是什么, entries.sorted() 都按声明的顺序返回常数:
Enum<T> 类提供了 compareTo(), equals() 和 hashCode() 函数, 你不能像在普通类中那样, 通过覆盖这些函数来自定义行为. 比较始终遵循声明顺序.
如果需要其他的顺序, 请不要依赖声明顺序. 应该定义明确的属性, 并按这个属性排序. 例如, 按亮度对颜色排序:
详情请参见 排序.
向枚举类添加函数
和拥有属性一样, 枚举类也可以拥有函数. 你可以添加所有枚举常数共通的函数, 将函数与属性结合, 或者定义操作符函数.
添加所有常数共通的函数
要添加所有枚举常数共通的行为, 请在枚举类的 Body 部中定义函数. 如果枚举类定义了成员, 在常数定义和成员定义之间请使用分号分隔:
每个枚举常数都可以调用这个共通函数. 在函数内部, this 指向调用这个函数的枚举常数.
可以将构造器属性与函数结合, 为每个常数关联数据, 添加使用这些数据的行为:
这里, 每个常数都保存它自己的 hex 值, 共通的 describe() 函数同时使用了内建的 name 属性和 hex 属性.
添加操作符函数
枚举类还可以定义 操作符函数, 然后你就可以对枚举常数使用操作符. 例如, 定义 not() 操作符函数, 通过 ! 操作符返回相反的方向:
使用匿名类
枚举常数可以声明自己的匿名类, 提供相应的函数, 也可以覆盖基类函数. 使用匿名类时, 在枚举常数名称后直接书写类的 Body 部, Kotlin 会将枚举类推断为它的超类型.
如果在枚举类中声明抽象函数, 并且要求每个常数提供自己的实现时, 这个功能会很有用. 每个常数都在自己的匿名类中覆盖这个抽象函数:
在这里, 每个常数对抽象函数 signal() 的实现都不同, 因此调用 signal() 时, 返回的下一个状态是什么, 取决于当前常数.
虽然枚举常数的行为类似于单例对象, 但枚举常数的类型是枚举类本身, 而不是它自己的匿名类. 因此, 不能访问匿名类 Body 部中声明的成员:
要为每个常数公开数据或行为, 请在枚举类的 Body 部中声明, 如果每个常数都需要自己的实现, 请使用抽象成员.
在枚举类中实现接口
枚举类可以实现接口, 但不能从其他类继承. 你可以为所有的枚举常数提供接口成员的共通实现, 也可以让每个常数在匿名类中提供自己的实现.
要实现接口, 请在枚举类的声明中添加接口:
在这个示例中, IntArithmetics 枚举类在声明中实现了 BinaryOperator<Int> 和 IntBinaryOperator 两个接口. PLUS 和 TIMES 都在自己的匿名类 Body 部中覆盖了接口的 apply() 成员, 而 applyAsInt() 为所有常数提供了共通的实现.