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 结构,可直接导入运行。


原始标题:Working with Enums in Kotlin