1. 概述
在这篇文章中,我们将深入探讨 Kotlin 中的枚举(enum)。
随着编程语言的发展,枚举的使用方式也发生了演变。
✅ 枚举常量如今不再仅仅是简单的常量集合 —— 它们可以拥有属性、实现接口,甚至具备复杂的行为逻辑。
2. Kotlin 枚举基础
2.1. 定义枚举
我们先来看一个基础的枚举定义,它包含三种信用卡类型:
enum class CardType {
SILVER, GOLD, PLATINUM
}
2.2. 初始化枚举常量
和 Java 一样,Kotlin 中的枚举也可以拥有构造函数。因为每个枚举常量本质上是枚举类的一个实例,所以我们可以给构造函数传递参数来初始化这些常量。
比如,为每种卡类型指定一个颜色:
enum class CardType(val color: String) {
SILVER("gray"),
GOLD("yellow"),
PLATINUM("black")
}
访问颜色值的方式如下:
val color = CardType.SILVER.color
3. 将枚举常量定义为匿名类
我们可以将枚举常量定义为匿名类,从而为每个常量定义特定的行为。此时,枚举类中需要声明抽象方法,每个常量都要实现该方法。
例如,不同类型的信用卡可能有不同的返现比例:
enum class CardType {
SILVER {
override fun calculateCashbackPercent() = 0.25f
},
GOLD {
override fun calculateCashbackPercent() = 0.5f
},
PLATINUM {
override fun calculateCashbackPercent() = 0.75f
};
abstract fun calculateCashbackPercent(): Float
}
调用方式如下:
val cashbackPercent = CardType.SILVER.calculateCashbackPercent()
4. 枚举实现接口
假设我们有一个接口 ICardLimit
,用于定义不同卡类型的信用额度:
interface ICardLimit {
fun getCreditLimit(): Int
}
枚举类可以实现这个接口:
enum class CardType : ICardLimit {
SILVER {
override fun getCreditLimit() = 100000
},
GOLD {
override fun getCreditLimit() = 200000
},
PLATINUM {
override fun getCreditLimit() = 300000
}
}
获取额度的方式如下:
val creditLimit = CardType.PLATINUM.getCreditLimit()
5. 常用枚举操作
5.1. 根据名称获取枚举常量
使用 valueOf()
方法可以通过字符串名称获取对应的枚举常量:
val cardType = CardType.valueOf(name.toUpperCase())
5.2. 遍历枚举常量
使用 values()
方法可以遍历所有枚举常量:
for (cardType in CardType.values()) {
println(cardType.color)
}
⚠️ 从 Kotlin 1.9.0 开始,推荐使用 entries
属性来遍历枚举常量,性能更好:
for (cardType in CardType.entries) {
println(cardType.color)
}
entries
返回的是一个 EnumEntries<CardType>
对象,是 List
的不可变实现:
val enumEntries: EnumEntries<CardType> = CardType.entries
Assertions.assertEquals(CardType.values().size, enumEntries.size)
还可以使用 find
方法查找符合条件的枚举常量:
val actualCardType = CardType.entries.find { it.color == "gray" }
Assertions.assertEquals(CardType.SILVER, actualCardType)
5.3. 静态方法
在 Kotlin 中没有真正的静态方法,但可以通过 companion object
来模拟:
companion object {
fun getCardTypeByName(name: String) = valueOf(name.toUpperCase())
}
调用方式如下:
val cardType = CardType.getCardTypeByName("SILVER")
6. 序数与非序数枚举
6.1. 序数枚举
枚举的 ordinal
属性表示其在类中的声明顺序(从 0 开始):
enum class Weekday {
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
}
验证 ordinal
:
Assertions.assertEquals(0, Weekday.SUNDAY.ordinal)
Assertions.assertEquals(1, Weekday.MONDAY.ordinal)
// ...
通过 ordinal
获取枚举常量:
Assertions.assertEquals(Weekday.SUNDAY, Weekday.values()[0])
compareTo()
方法也基于 ordinal
:
Assertions.assertEquals(1, Weekday.MONDAY.compareTo(Weekday.SUNDAY))
6.2. 非序数枚举
非序数枚举是指其内在含义与 ordinal
无关的枚举。
比如定义扑克牌的花色和点数:
enum class Suit {
HEARTS, DIAMONDS, CLUBS, SPADES
}
enum class Rank(val value: Int) {
ACE(1),
TWO(2),
// ...
KING(13)
}
定义一张牌:
enum class PlayingCard(val rank: Rank, val suit: Suit) {
KING_OF_SPADES(Rank.KING, Suit.SPADES),
QUEEN_OF_DIAMONDS(Rank.QUEEN, Suit.DIAMONDS);
}
由于 compareTo()
方法是 final
的,无法自定义排序逻辑,我们可以使用 Comparator
:
val rankComparator = Comparator<Rank> { r1, r2 ->
r1.value - r2.value
}
val playingCardComparator = Comparator<PlayingCard> { pc1, pc2 ->
rankComparator.compare(pc1.rank, pc2.rank)
}
排序验证:
val playingCards = listOf(PlayingCard.KING_OF_SPADES, PlayingCard.QUEEN_OF_DIAMONDS)
val sortedPlayingCards = playingCards.sortedWith(playingCardComparator)
Assertions.assertEquals(PlayingCard.QUEEN_OF_DIAMONDS, sortedPlayingCards[0])
Assertions.assertEquals(PlayingCard.KING_OF_SPADES, sortedPlayingCards[1])
7. 扩展枚举
7.1. 枚举无法被继承
枚举类在 Kotlin 中是 final
的,无法被继承:
val weekdayClazz = Weekday::class.java
val isFinal = java.lang.reflect.Modifier.isFinal(weekdayClazz.modifiers)
Assertions.assertTrue(isFinal)
尝试继承会报错:
enum class MyWeekday: Weekday {
}
编译报错如下:
[ERROR] Enum class cannot inherit from classes
[ERROR] This type is final, so it cannot be inherited from
7.2. 使用接口扩展行为
虽然不能继承枚举,但可以通过接口扩展行为。
定义接口:
interface IColor {
fun type(): ColorType
fun paint(): String
}
定义枚举类:
enum class PrimaryColor : IPrimaryColor {
RED {
override fun paint(): String {
return "red"
}
};
}
运行时多态验证:
val colors = listOf(PrimaryColor.RED, SecondaryColor.GREEN)
for(color in colors) {
Assertions.assertTrue(color is IColor)
val myColor = color.paint()
Assertions.assertNotNull(myColor)
}
7.3. 使用密封类扩展行为
密封类是枚举的泛化版本,适用于对象数量固定但行为各异的场景:
sealed class Color {
abstract fun type(): ColorType
abstract fun paint(): String
}
定义具体对象:
object RED : Color() {
override fun paint(): String {
return "red"
}
override fun type(): ColorType {
return ColorType.PRIMARY
}
}
验证行为:
val colors = listOf(Color.RED, Color.GREEN)
for (color in colors) {
val myColor = color.paint()
Assertions.assertNotNull(myColor)
}
8. 总结
本文介绍了 Kotlin 中枚举的基础与高级用法,包括:
- 枚举定义与初始化
- 匿名类与接口实现
- 枚举的遍历与查找
- 序数与非序数枚举的区别
- 使用接口与密封类扩展枚举行为
所有示例代码可在 GitHub 项目 中找到,项目为 Maven 结构,可直接导入运行。