在上一章中,我们使用 Kotlin 中一个叫做类的特性创建了我们自己的类型,叫做Circle。在本章中,我们将了解一种特殊的类——枚举类——当你想要表示有限数量的值时,它特别有用。
限制值
我最喜欢的狗是雪纳瑞。事实上,就在我写这一章的时候,有一只正躺在我身边!她对我敲键盘的咔哒声有点烦躁,因为她正想打个盹,但等会儿我们玩抛球的时候我会好好补偿她的。
总之,让我们创建一个String变量来保存宠物雪纳瑞的名字,像这样:
val nameOfSchnauzer: String = "Shadow"你能想到多少种不同的雪纳瑞名字?我能想到Shadow、Rover和Captain 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而不是单独的class。 - 之后是我们想要给类的名字——在本例中是
SchnauzerBreed。 - 在类的内部,选项被称为枚举条目。有时我们也称它们为枚举常量。它们用逗号分隔。
使用枚举类
现在我们有了枚举类,可以开始使用它了。请注意,你不能以与普通类相同的方式从枚举类构造对象,否则会收到这样的错误:
val breed: SchnauzerBreed = SchnauzerBreed()相反,你只需将变量赋值为其中一个选项,像这样:
val breed: SchnauzerBreed = SchnauzerBreed.GIANT通过使用枚举类在这里,如果我们不小心输入了MINI而不是MINIATURE,那么 Kotlin 会在我们运行代码之前就给出错误!
val breed: SchnauzerBreed = SchnauzerBreed.MINI所以我们没有使用可以被设置为几乎任何东西的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"
}要修复这个错误,我们要么必须提供所有枚举常量,就像我们在清单 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厘米高。
注意,枚举条目是枚举类的实例。
你可以像对待常规对象一样从枚举实例获取属性。例如,我们可以像这样将一个品种的高度打印到屏幕:
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是列表中的第二个条目,所以它的ordinal是1。
是的,它从零开始,如果你以前没怎么做过编程,这可能会有点令人困惑。一旦我们谈到集合的主题,我们会看到从零开始的编号在编程中是如何广泛使用的!
name
如果你需要知道枚举条目的名称作为字符串,你可以使用name属性。
为了演示这一点,我们可以创建一个函数,告诉我们品种的名称以及身高。
fun describe(breed: SchnauzerBreed) {
println(breed.name)
println(breed.height)
}现在,我们可以调用describe(SchnauzerBreed.STANDARD),我们会在屏幕上看到以下内容:
STANDARD 47
总结
在本章中,我们学习了:
- 限制值的范围如何帮助确保我们的程序正确编写
- 如何创建枚举类
- 如何将枚举类与 “when” 条件结合使用
- 如何向枚举类添加属性和函数
- 如何使用枚举类的某些内置属性
恭喜你!自从第一章以来,你已经取得了很大的进步!请继续关注下一章中的空值和空安全!
感谢 James Lorenzen 和 Mohit 审阅本章!