1. 上一章:第 4 章
  2. 下一章:第 6 章
Kotlin 图解指南 • 第 5 章

枚举类

章节封面图片
是的,上一章也有一幅狗狗的插图...

上一章中,我们使用 Kotlin 中一个叫做的特性创建了我们自己的类型,叫做Circle。在本章中,我们将了解一种特殊的类——枚举类——当你想要表示有限数量的值时,它特别有用。

限制值

我最喜欢的狗是雪纳瑞。事实上,就在我写这一章的时候,有一只正躺在我身边!她对我敲键盘的咔哒声有点烦躁,因为她正想打个盹,但等会儿我们玩抛球的时候我会好好补偿她的。

总之,让我们创建一个String变量来保存宠物雪纳瑞的名字,像这样:

val nameOfSchnauzer: String = "Shadow"

你能想到多少种不同的雪纳瑞名字?我能想到ShadowRoverCaptain Fluffybeard,但实际上可以给它们起的名字种类没有限制。可能性是无限的!

然而,雪纳瑞品种就是另一回事了!雪纳瑞只有3个品种:

  • 迷你雪纳瑞
  • 标准雪纳瑞
  • 巨型雪纳瑞

所以,虽然雪纳瑞名字的数量是无限的,但品种的数量有限,只有3个选项。

这一切与编程有什么关系?

当我们在编写代码时,通过选择一种将可能值的范围限制为仅有效值的类型,我们可以减少出错的可能性。

例如,String可以保存你几乎能想到的任何文本。既然你可以给狗起几乎任何你能想到的名字,那么使用String来保存它的名字是合理的。

另一方面,String对于保存雪纳瑞的特定品种来说不是一个好的选择。为什么?因为字符串可以保存无限数量的不同值,但雪纳瑞品种只有3个可能的正确值。换句话说,无法保证你将品种变量设置为这三个正确值之一。

让我们通过一个例子来演示这一点。

假设我们决定使用String来表示品种,像这样:

val breedOfSchnauzer: String = "Mini"

现在,在代码的其他地方,我们可能有一个检查品种的条件,像这样:

if (breedOfSchnauzer == "Miniature") {
    // ... do something
}

我们可能本来期望条件表达式的结果为true,但由于我们错误地输入了"Mini"而不是"Miniature",它计算结果为false……在运行代码之前我们不会知道有任何问题。

问题在于我们使用了一种允许无限值的类型(即String类型),而我们需要的只是一种允许有限数量值的类型。如果我们可以创建一种只允许你使用三个品种名称之一类型,就可以使上述错误根本不可能发生!

要在 Kotlin 中创建具有有限值的类型,你可以使用枚举类……或者我们更常称它为枚举类,或者简称为枚举

创建枚举类

让我们创建一个枚举类来表示雪纳瑞的品种。

enum class SchnauzerBreed {
    MINIATURE,
    STANDARD,
    GIANT
}

这个枚举类被命名为SchnauzerBreed,它给我们个品种选项可供选择。

以下是enum class的主要部分:

enum 和 class 是关键字,SchnauzerBreed 是类名,MINIATURE、STANDARD 和 GIANT 是枚举条目。
  1. 要创建枚举类,我们写enum class而不是单独的class
  2. 之后是我们想要给类的名字——在本例中是SchnauzerBreed
  3. 在类的内部,选项被称为枚举条目。有时我们也称它们为枚举常量。它们用逗号分隔。

使用枚举类

现在我们有了枚举类,可以开始使用它了。请注意,你不能以与普通类相同的方式从枚举类构造对象,否则会收到这样的错误:

val breed: SchnauzerBreed = SchnauzerBreed()
Error

相反,你只需将变量赋值为其中一个选项,像这样:

val breed: SchnauzerBreed = SchnauzerBreed.GIANT

通过使用枚举类在这里,如果我们不小心输入了MINI而不是MINIATURE,那么 Kotlin 会在我们运行代码之前就给出错误!

val breed: SchnauzerBreed = SchnauzerBreed.MINI
Error

所以我们没有使用可以被设置为几乎任何东西String,而是将有效选项限制为三个枚举条目。通过这样做,Kotlin 现在可以帮助我们发现输入错误,而不必等到运行代码!

when 表达式中使用枚举类

迷你雪纳瑞的卡通形象

使用枚举类的另一个好处是,当我们将其与when 条件一起使用时,Kotlin 会提供一些额外的帮助。

你可能还记得,when 表达式必须考虑每种情况——也就是说,它们必须是穷尽的。因为枚举类限制了可能的值,Kotlin 可以利用这些信息来知道我们确实已经考虑了每种情况。

例如,这里有一些返回品种描述的代码。

fun describe(breed: SchnauzerBreed) = when (breed) {
    SchnauzerBreed.MINIATURE -> "Small"
    SchnauzerBreed.STANDARD  -> "Medium"
    SchnauzerBreed.GIANT     -> "Large"
}

注意这里没有else条件!Kotlin 可以看出我们在when体中已经包含了全部三个枚举条目,所以没有其他可能性。

如果我们省略一些条目,Kotlin 会给我们一个错误:

fun describe(breed: SchnauzerBreed) = when (breed) {
    SchnauzerBreed.MINIATURE -> "Small"
    SchnauzerBreed.STANDARD  -> "Medium"
}
Error

要修复这个错误,我们要么必须提供所有枚举常量,就像我们在清单 5.8中所做的那样,要么我们必须提供一个else条件,像这样:

fun describe(breed: SchnauzerBreed) = when (breed) {
    SchnauzerBreed.MINIATURE -> "Small"
    SchnauzerBreed.STANDARD  -> "Medium"
    else                     -> "Unknown"            
}

当你的when不是穷尽的时候,Kotlin 会给你一个错误,这很好。例如,每当你向现有枚举类添加新条目时,Kotlin 会在所有需要更新的when表达式中给出错误——所以你会确切地知道哪些代码需要更改!

向枚举类添加属性和函数

标准雪纳瑞的卡通形象

你还记得上一章中,普通 Kotlin 类允许我们将属性和函数放在一起。是否也可以向枚举类添加属性和函数?

是的,可以!

让我们首先添加一个作为构造函数参数的属性。我们可以像对普通类那样做——通过把它们放在类名后面打开的(和关闭的)括号之间。

例如,我们可能想包含每个品种的近似身高,以厘米为单位。我们可以这样做:

enum class SchnauzerBreed(val height: Int) {
    MINIATURE(33),
    STANDARD(47),
    GIANT(65)
}

因为我们添加了一个名为height的新构造函数参数,我们也必须为每个枚举条目添加一个构造函数参数。所以迷你型的近似身高是33厘米,标准型大约是47厘米,巨型大约是65厘米高。

height 是一个构造函数参数,33、47 和 65 是构造函数参数。

注意,枚举条目是枚举类的实例

你可以像对待常规对象一样从枚举实例获取属性。例如,我们可以像这样将一个品种的高度打印到屏幕:

println(SchnauzerBreed.MINIATURE.height)

我们也可以包含一个不需要构造函数参数的属性。让我们添加一个属性,告诉我们它们都属于哪个品种的family

enum class SchnauzerBreed(val height: Int) {
    MINIATURE(33),
    STANDARD(47),
    GIANT(65);

    val family: String = "Schnauzer"        
}

新的family属性被添加到类体的末尾。需要注意的是,分号;在最后一个枚举条目之后是必需的!在 Kotlin 中,你必须使用分号的情况并不多,但这是其中之一。

添加函数同样简单。和上面一样,确保包含分号,然后在枚举条目下面写函数。

enum class SchnauzerBreed(val height: Int) {
    MINIATURE(33),
    STANDARD(47),
    GIANT(65);

    val family: String = "Schnauzer"

    fun isShorterThan(centimeters: Int) = height < centimeters
}

你可以像对任何其他类一样获取属性和调用函数。

println(SchnauzerBreed.STANDARD.family)             // Prints "Schnauzer"
println(SchnauzerBreed.STANDARD.isShorterThan(40))  // Prints "false"

内置属性

巨型雪纳瑞的卡通形象

除了你自己包含的属性外,Kotlin 还会自动向所有枚举实例添加几个属性。

ordinal

所有枚举实例都有一个名为ordinal的属性。你可以用它来告诉你这个枚举条目在列表中的位置。列表中的第一个条目的 ordinal 是0,第二个是1,第三个是2,以此类推。

例如,因为STANDARD是列表中的第二个条目,所以它的ordinal1

MINIATURE 的 ordinal 是 0,STANDARD 的 ordinal 是 1,GIANT 的 ordinal 是 2。

是的,它从开始,如果你以前没怎么做过编程,这可能会有点令人困惑。一旦我们谈到集合的主题,我们会看到从零开始的编号在编程中是如何广泛使用的!

name

如果你需要知道枚举条目的名称作为字符串,你可以使用name属性。

为了演示这一点,我们可以创建一个函数,告诉我们品种的名称以及身高。

fun describe(breed: SchnauzerBreed) {
    println(breed.name)
    println(breed.height)
}

现在,我们可以调用describe(SchnauzerBreed.STANDARD),我们会在屏幕上看到以下内容:

STANDARD
47

总结

在本章中,我们学习了:

恭喜你!自从第一章以来,你已经取得了很大的进步!请继续关注下一章中的空值空安全

感谢 James LorenzenMohit 审阅本章!