5.5.1 条件与循环
原文链接: https://kotlinlang.org/docs/control-flow.html
5.5.1 条件与循环
Kotlin 为你提供了灵活的工具来控制程序流程。使用 if、when 和循环,可以为你的条件定义清晰且富有表现力的逻辑。
If 表达式
在 Kotlin 中使用 if 时,把要检查的条件写在圆括号 () 中,把结果为真时要执行的操作写在花括号 {} 中。你可以用 else 和 else if 添加更多分支和检查。
你也可以把 if 写成表达式,这样就能把它的返回值直接赋给变量。在这种形式下,else 分支是必需的。if 表达式的作用与其他语言中的三元运算符(condition ? then : else)相同。
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| fun main() {
val heightAlice = 160
val heightBob = 175
var taller = heightAlice
if (heightAlice < heightBob) taller = heightBob
// 使用 else 分支
if (heightAlice > heightBob) {
taller = heightAlice
} else {
taller = heightBob
}
// 把 if 用作表达式
taller = if (heightAlice > heightBob) heightAlice else heightBob
// 把 else if 用作表达式:
val heightLimit = 150
val heightOrLimit = if (heightLimit > heightAlice) heightLimit else if (heightAlice > heightBob) heightAlice else heightBob
println("Taller height is $taller")
// 较高的身高是 175
println("Height or limit is $heightOrLimit")
// 身高或上限是 175
}
|
if 表达式中的每个分支都可以是一个代码块,此时最后一个表达式的值就是该分支的结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| fun main() {
val heightAlice = 160
val heightBob = 175
val taller = if (heightAlice > heightBob) {
print("Choose Alice\n")
heightAlice
} else {
print("Choose Bob\n")
heightBob
}
println("Taller height is $taller")
}
|
when 表达式与语句
when 是一种条件表达式,它会根据多个可能的值或条件来执行代码。它类似于 Java、C 等语言中的 switch 语句。when 会计算其参数,并按顺序与每个分支比较,直到某个分支条件得到满足。例如:
1
2
3
4
5
6
7
8
9
| fun main() {
val userRole = "Editor"
when (userRole) {
"Viewer" -> print("User has read-only access")
"Editor" -> print("User can edit content")
else -> print("User role is not recognized")
}
// 用户可以编辑内容
}
|
when 既可以作为表达式使用,也可以作为语句使用。作为表达式时,when 会返回一个值,供你在后续代码中使用;作为语句时,when 只完成一个操作,不返回结果:
| 表达式 | 语句 |
|---|
kotlin // 返回一个字符串并赋给 // text 变量 val text = when (x) { 1 -> "x == 1" 2 -> "x == 2" else -> "x is neither 1 nor 2" } | kotlin // 不返回结果,只触发一条 // 打印语句 when (x) { 1 -> print("x == 1") 2 -> print("x == 2") else -> print("x is neither 1 nor 2") } |
其次,when 可以带主语(subject),也可以不带。两种情况的行为相同。使用主语通常会让代码更易读、更易维护,因为它清楚地表明了你在检查什么。
带主语 x | 不带主语 |
|---|
kotlin when(x) { ... } | kotlin when { ... } |
你如何使用 when 决定了分支中是否需要覆盖所有可能的情况。覆盖所有可能情况称为穷尽(exhaustive)。
语句
如果把 when 当作语句使用,就不需要覆盖所有可能的情况。在下面这个例子中,有些情况没有被覆盖,因此不会触发任何分支,但也不会产生错误:
1
2
3
4
5
6
7
8
| fun main() {
val deliveryStatus = "OutForDelivery"
when (deliveryStatus) {
// 并非所有情况都被覆盖
"Pending" -> print("Your order is being prepared")
"Shipped" -> print("Your order is on the way")
}
}
|
与 if 一样,每个分支都可以是一个代码块,其值就是代码块中最后一个表达式的值。
表达式
如果把 when 当作表达式使用,就必须覆盖所有可能的情况。第一个匹配分支的值就是整个表达式的值。如果没有覆盖所有情况,编译器会报错。
如果你的 when 表达式带有主语,可以用 else 分支来确保覆盖所有可能的情况,但这不是必需的。例如,当主语是 Boolean、enum 类、sealed 类或它们的可空形式时,可以不用 else 分支就覆盖所有情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import kotlin.random.Random
enum class Bit {
ZERO, ONE
}
fun getRandomBit(): Bit {
return if (Random.nextBoolean()) Bit.ONE else Bit.ZERO
}
fun main() {
val numericValue = when (getRandomBit()) {
// 不需要 else 分支,因为所有情况都已覆盖
Bit.ZERO -> 0
Bit.ONE -> 1
}
println("Random bit as number: $numericValue")
// 随机比特值:0
}
|
提示: 要简化 when 表达式并减少重复,可以试试上下文敏感解析(目前为预览功能)。当预期类型已知时,该功能允许你在 when 表达式中省略枚举项或密封类成员的类型名。更多信息请参阅上下文敏感解析预览或相关的 KEEP 提案。
如果你的 when 表达式没有主语,就必须有 else 分支,否则编译器会报错。当其他分支条件都不满足时,会执行 else 分支:
1
2
3
4
5
6
7
8
9
10
11
12
13
| fun main() {
val localFileSize = 1200
val remoteFileSize = 1200
val message = when {
localFileSize > remoteFileSize -> "Local file is larger than remote file"
localFileSize < remoteFileSize -> "Local file is smaller than remote file"
else -> "Local and remote files are the same size"
}
println(message)
// 本地文件和远程文件大小相同
}
|
when 的其他用法
when 表达式和语句提供了多种方式来简化代码、处理多个条件并执行类型检查。
用逗号可以把多个条件归入同一个分支:
1
2
3
4
5
6
7
| fun main() {
val ticketPriority = "High"
when (ticketPriority) {
"Low", "Medium" -> print("Standard response time")
else -> print("High-priority handling")
}
}
|
把求值结果为 true 或 false 的表达式用作分支条件:
1
2
3
4
5
6
7
8
9
10
| fun main() {
val storedPin = "1234"
val enteredPin = 1234
when (enteredPin) {
// 表达式
storedPin.toInt() -> print("PIN is correct")
else -> print("Incorrect PIN")
}
}
|
使用 in 或 !in 关键字检查某个值是否在区间或集合中:
1
2
3
4
5
6
7
8
9
10
11
| fun main() {
val x = 7
val validNumbers = setOf(15, 16, 17)
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
}
|
使用 is 或 !is 关键字检查值的类型。由于有智能转换,你可以直接访问该类型的成员函数和属性:
1
2
3
4
5
6
7
8
9
10
| fun hasPrefix(input: Any): Boolean = when (input) {
is String -> input.startsWith("ID-")
else -> false
}
fun main() {
val testInput = "ID-98345"
println(hasPrefix(testInput))
// true
}
|
可以用 when 代替传统的 if-else if 链。不带主语时,分支条件就是布尔表达式,第一个条件为 true 的分支会被执行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| fun Int.isOdd() = this % 2 != 0
fun Int.isEven() = this % 2 == 0
fun main() {
val x = 5
val y = 8
when {
x.isOdd() -> print("x is odd")
y.isEven() -> print("y is even")
else -> print("x+y is odd")
}
// x 是奇数
}
|
最后,可以用下面的语法把主语捕获到变量中:
1
2
3
4
5
6
7
8
9
10
| fun main() {
val message = when (val input = "yes") {
"yes" -> "You said yes"
"no" -> "You said no"
else -> "Unrecognized input: $input"
}
println(message)
// 你说了 yes
}
|
作为主语引入的变量,其作用域仅限于该 when 表达式或语句的主体。
守卫条件
守卫条件(guard condition)允许你为 when 表达式或语句的分支指定多个条件,从而让复杂的控制流更明确、更简洁。只要 when 带有主语,就可以使用守卫条件。
把守卫条件放在同一分支中主条件之后,用 if 分隔:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| sealed interface Animal {
data class Cat(val mouseHunter: Boolean) : Animal
data class Dog(val breed: String) : Animal
}
fun feedDog() = println("Feeding a dog")
fun feedCat() = println("Feeding a cat")
fun feedAnimal(animal: Animal) {
when (animal) {
// 只有主条件的分支
// 当 animal 是 Dog 时调用 feedDog()
is Animal.Dog -> feedDog()
// 同时包含主条件和守卫条件的分支
// 当 animal 是 Cat 且不是 mouseHunter 时调用 feedCat()
is Animal.Cat if !animal.mouseHunter -> feedCat()
// 如果以上条件都不匹配,则打印 "Unknown animal"
else -> println("Unknown animal")
}
}
fun main() {
val animals = listOf(
Animal.Dog("Beagle"),
Animal.Cat(mouseHunter = false),
Animal.Cat(mouseHunter = true)
)
animals.forEach { feedAnimal(it) }
// 正在喂狗
// 正在喂猫
// 未知的动物
}
|
当存在用逗号分隔的多个条件时,不能使用守卫条件。例如:
1
| 0, 1 -> print("x == 0 or x == 1")
|
在同一个 when 表达式或语句中,你可以混用带守卫条件和不带守卫条件的分支。带守卫条件的分支只有在主条件和守卫条件都为 true 时才会执行。如果主条件不匹配,守卫条件不会被求值。
由于 when 语句不需要覆盖所有情况,所以在没有 else 分支的 when 语句中使用守卫条件意味着:如果没有任何条件匹配,就不会执行任何代码。
与语句不同,when 表达式必须覆盖所有情况。如果在没有 else 分支的 when 表达式中使用守卫条件,编译器会要求你处理所有可能的情况,以避免运行时错误。
可以使用布尔运算符 &&(与)或 ||(或)在单个分支中组合多个守卫条件。请用圆括号把布尔表达式括起来,以避免歧义:
1
2
3
| when (animal) {
is Animal.Cat if (!animal.mouseHunter && animal.hungry) -> feedCat()
}
|
守卫条件也支持 else if:
1
2
3
4
5
6
7
8
9
10
| when (animal) {
// 检查 `animal` 是否为 `Dog`
is Animal.Dog -> feedDog()
// 守卫条件:检查 `animal` 是否为 `Cat` 且不是 `mouseHunter`
is Animal.Cat if !animal.mouseHunter -> feedCat()
// 如果以上条件都不匹配且 animal.eatsPlants 为 true,则调用 giveLettuce()
else if animal.eatsPlants -> giveLettuce()
// 如果以上条件都不匹配,则打印 "Unknown animal"
else -> println("Unknown animal")
}
|
JVM 上的字节码生成
当你为 JVM 21 或更高版本编译 Kotlin 代码时,编译器会为符合条件的 when 表达式生成 invokedynamic 指令。这会生成更小的字节码,与 Java switch 语句生成的字节码类似。
当满足以下所有条件时,编译器会使用 invokedynamic 以及 SwitchBootstraps.typeSwitch() 方法:
- 除
else 之外的所有条件都是 is 或 null 检查。 when 表达式不包含守卫条件(if)。- 条件中不包含无法直接进行类型检查的类型,例如可变 Kotlin 集合(
MutableList)或函数类型(kotlin.Function1、kotlin.Function2 等)。 when 表达式除 else 之外至少还有两个条件。- 所有分支检查的都是
when 表达式的同一个主语。
例如:
1
2
3
4
5
6
7
8
9
10
11
12
| open class Shape
class Circle : Shape()
class Rectangle : Shape()
class Triangle : Shape()
fun countCorners(shape: Shape) = when (shape) {
is Circle -> 0
is Rectangle -> 4
is Triangle -> 3
else -> -1
}
|
这里的 when (shape) 表达式在字节码中会编译成单个 invokedynamic 类型 switch,而不是多次 instanceof 检查。
for 循环
使用 for 循环遍历集合、数组或区间:
1
| for (item in collection) print(item)
|
for 循环的主体可以是带花括号 {} 的代码块。
1
2
3
4
5
6
7
8
9
10
11
| fun main() {
val shoppingList = listOf("Milk", "Bananas", "Bread")
println("Things to buy:")
for (item in shoppingList) {
println("- $item")
}
// 要买的东西:
// - 牛奶
// - 香蕉
// - 面包
}
|
区间
要遍历一个数字区间,请使用带 .. 和 ..< 运算符的区间表达式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| fun main() {
println("Closed-ended range:")
for (i in 1..6) {
print(i)
}
// 闭区间:
// 123456
println("\nOpen-ended range:")
for (i in 1..<6) {
print(i)
}
// 开区间:
// 12345
println("\nReverse order in steps of 2:")
for (i in 6 downTo 0 step 2) {
print(i)
}
// 以步长 2 逆序:
// 6420
}
|
数组
如果你想带索引遍历数组或列表,可以使用 indices 属性:
1
2
3
4
5
6
7
8
9
| fun main() {
val routineSteps = arrayOf("Wake up", "Brush teeth", "Make coffee")
for (i in routineSteps.indices) {
println(routineSteps[i])
}
// 起床
// 刷牙
// 煮咖啡
}
|
或者,你可以使用标准库中的 .withIndex() 函数:
1
2
3
4
5
6
7
8
9
| fun main() {
val routineSteps = arrayOf("Wake up", "Brush teeth", "Make coffee")
for ((index, value) in routineSteps.withIndex()) {
println("The step at $index is \"$value\"")
}
// 第 0 步是 "Wake up"
// 第 1 步是 "Brush teeth"
// 第 2 步是 "Make coffee"
}
|
迭代器
for 循环可以遍历任何提供迭代器的对象。集合默认提供迭代器,而区间和数组会被编译成基于索引的循环。
你可以通过提供名为 iterator() 的成员函数或扩展函数(返回 Iterator<>)来创建自己的迭代器。iterator() 函数必须有一个 next() 函数和一个返回 Boolean 的 hasNext() 函数。
为类创建自定义迭代器最简单的方式,是继承 Iterable<T> 接口,并重写其中已有的 iterator()、next() 和 hasNext() 函数。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| class Booklet(val totalPages: Int) : Iterable<Int> {
override fun iterator(): Iterator<Int> {
return object : Iterator<Int> {
var current = 1
override fun hasNext() = current <= totalPages
override fun next() = current++
}
}
}
fun main() {
val booklet = Booklet(3)
for (page in booklet) {
println("Reading page $page")
}
// 正在读第 1 页
// 正在读第 2 页
// 正在读第 3 页
}
|
提示: 进一步了解接口和继承。
你也可以完全从头创建这些函数。这种情况下,需要给函数加上 operator 关键字:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| class Booklet(val totalPages: Int) {
operator fun iterator(): Iterator<Int> {
return object {
var current = 1
operator fun hasNext() = current <= totalPages
operator fun next() = current++
}.let {
object : Iterator<Int> {
override fun hasNext() = it.hasNext()
override fun next() = it.next()
}
}
}
}
fun main() {
val booklet = Booklet(3)
for (page in booklet) {
println("Reading page $page")
}
// 正在读第 1 页
// 正在读第 2 页
// 正在读第 3 页
}
|
while 循环
while 和 do-while 循环会在条件满足时不断执行循环体中的代码。两者的区别在于检查条件的时机:
while 先检查条件,如果满足,就执行循环体中的代码,然后回到条件检查。do-while 先执行循环体中的代码,然后检查条件。如果满足,就重复循环。因此,无论条件如何,do-while 的循环体至少会执行一次。
对于 while 循环,把要检查的条件放在圆括号 () 中,把循环体放在花括号 {} 中:
1
2
3
4
5
6
7
8
9
10
11
12
13
| fun main() {
var carsInGarage = 0
val maxCapacity = 3
while (carsInGarage < maxCapacity) {
println("Car entered. Cars now in garage: ${++carsInGarage}")
}
// 车辆已进入。车库中现有车辆:1
// 车辆已进入。车库中现有车辆:2
// 车辆已进入。车库中现有车辆:3
println("Garage is full!")
// 车库已满!
}
|
对于 do-while 循环,先把循环体放在花括号 {} 中,然后再把要检查的条件放在圆括号 () 中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| import kotlin.random.Random
fun main() {
var roll: Int
do {
roll = Random.nextInt(1, 7)
println("Rolled a $roll")
} while (roll != 6)
// 掷出了 2
// 掷出了 6
println("Got a 6! Game over.")
// 得到 6!游戏结束。
}
|
循环中的 break 与 continue
Kotlin 在循环中支持传统的 break 和 continue 运算符。请参阅返回与跳转。