《Kotlin核心编程》 ——2.4.4 枚举类和when表达式
2.4.4 枚举类和when表达式
本节主要介绍Kotlin中另一种非常强大的表达式—when表达式。在了解它之前,我们先来看看在Kotlin中如何定义枚举结构,然后再使用when结合枚举更好地来设计业务,并介绍when表达式的具体语法。
1.枚举是类
在Kotlin中,枚举是通过一个枚举类来实现的。先来实现一个很简单的例子:
enum class Day {
MON,
TUE,
WEN,
THU,
FRI,
SAT,
SUN
}
与Java中的enum语法大体相似,无非多了一个class关键词,表示它是一个枚举类。不过Kotlin中的枚举类当然没那么简单,由于它是一种类,我们可以猜测它自然应该可以拥有构造参数,以及定义额外的属性和方法。
enum class DayOfWeek(val day: Int) {
MON(1),
TUE(2),
WEN(3),
THU(4),
FRI(5),
SAT(6),
SUN(7)
; // 如果以下有额外的方法或属性定义,则必须强制加上分号
fun getDayNumber(): Int {
return day
}
}
需要注意的是,当在枚举类中存在额外的方法或属性定义,则必须强制加上分号,虽然你很可能不会喜欢这个语法。
枚举类“分号”语法的由来
早期枚举类的语法并没有逗号,然而却有点烦琐:
enum class DayOfWeek(val day: Int) {
MON: DayOfWeek(1)
TUE: DayOfWeek(2)
WEN: DayOfWeek(3)
THU: DayOfWeek(4)
FRI: DayOfWeek(5)
SAT: DayOfWeek(6)
SUN: DayOfWeek(7)
}
每个枚举值都需要通过DayOfWeek(n)来构造,这确实显得多余。理想的情况是我们只需调用MON(1)来表示即可。然而,简化语法后也带来了一些技术上的问题,比如在枚举类源码实现上很难把具体的枚举值与类方法进行区分。解决这个问题有好几种思路,第一种办法就是把每个方法都加上一个注解前缀,例如:
@inject fun getDayNumber(): Int {
return day
}
但是这样子就与其他的类在语法上显得不一样,破坏了语法的一致性。好吧,那么能不能反过来,给每个枚举类弄个关键词前缀来区分,比如:
entry MON(1)
显然,这样也不好。因为枚举值的数量无法控制,如果数量较多,会显得啰唆。Kotlin最终采用了引入逗号和分号的语法,即通过逗号对每个枚举值进行分隔,这样就可以最终采用一个分号来对额外定义的属性和方法进行隔离。
这确实是相对更合理的设计方案,尤其是加上逗号之后,Kotlin中的枚举类语法跟Java的枚举更相似了,这符合Kotlin的设计原则。
2.用when来代替if-else
在了解如何声明一个枚举类后,我们再来用它设计一个业务逻辑。比如,Shaw给新一周的几天计划了不同的活动,安排如下:
周六打篮球
周日钓鱼
星期五晚上约会
平日里如果天晴就去图书馆看书,不然就在寝室学习
他设计了一段代码,利用一个函数结合本节最开头的枚举类Day来进行表示:
fun schedule(day: Day, sunny: Boolean) = {
if (day == Day.SAT) {
basketball()
} else if (day == Day.SUN) {
fishing()
} else if (day == Day.FRI) {
appointment()
} else {
if (sunny) {
library()
} else {
study()
}
}
}
因为存在不少if-else分支,代码显得不够优雅。对Kotlin日渐熟悉的Shaw开始意识到,更好的改进方法就是用when表达式来优化。现在我们来看看修改后的版本:
fun schedule(sunny: Boolean, day: Day) = when (day) {
Day.SAT -> basketball()
Day.SUN -> fishing()
Day.FRI -> appointment()
else -> when {
sunny -> library()
else -> study()
}
}
整个函数一下子“瘦身”了很多,由于少了很多语法关键字干扰,代码的可读性也更上了一层楼。
3. when表达式具体语法
我们根据上述这段代码来分析下when表达式的具体语法:
1)一个完整的when表达式类似switch语句,由when关键字开始,用花括号包含多个逻辑分支,每个分支由->连接,不再需要switch的break(这真是一个恼人的关键字),由上到下匹配,一直匹配完为止,否则执行else分支的逻辑,类似switch的default;
2)每个逻辑分支具有返回值,最终整个when表达式的返回类型就是所有分支相同的返回类型,或公共的父类型。在上面的例子中,假设所有活动函数的返回值为Unit,那么编译器就会自动推导出when表达式的类型,即Unit。以下是一个非Unit的例子:
fun foo(a: Int) = when (a) {
1 -> 1
2 -> 2
else -> 0
}
>>> foo(1)
1
3)when关键字的参数可以省略,如上述的子when表达式可改成:
when {
sunny -> library()
else -> study()
}
该情况下,分支->的左侧部分需返回布尔值,否则编译会报错,如:
>>> when { 1 -> 1 }
error: condition must be of type kotlin.Boolean, but is of type kotlin.Int
4)表达式可以组合,所以这是一个典型的when表达式组合的例子。你在Java中很少见过这么长的表达式,但是这在Kotlin中很常见。如果你足够仔细,还会看出这还是一个我们之前提到过的表达式函数体。
可能你会说,这样嵌套子when表达式,层次依旧比较深。要知道when表达式是很灵活的,我们很容易通过如下修改来解决这个问题:
fun schedule(sunny: Boolean, day: Day) = when {
day == Day.SAT -> basketball()
day == Day.SUN -> fishing()
day == Day.FRI -> appointment()
sunny -> library()
else -> study()
}
是不是很优雅?其实when表达式的威力远不止于此。关于它更多的语法细节,我们会在第4章进一步介绍。同时你也将了解到如何利用when表达式结合代数数据类型,来对业务进行更好的抽象。
- 点赞
- 收藏
- 关注作者
评论(0)