Kotlin 控制流(一):条件和循环

Kotlin 为我们开发者提供了很多灵活的工具来控制程序的运转流程。开发者使用 if、when、循环来定义清晰易懂的逻辑。

If 表达式

Kotlin 的 if 使用方法,把条件添加到括号() 中,如果花括号是 true,就把对应的执行写到花括号{}中。对于其他的分支和检查,可以写道 else 和 else if 中。

开发者也可以把 if 作为表达式的形式,这样就可以把 if 的返回值赋值给变量。在这种用法下,else 分支是必须存在。 if 表达式的作用就是其他语言的三目运算:(condition ? then : else)

比如:

kotlin 复制代码
var taller = heightAlice
if (heightAlice < heightBob) taller = heightBob

// Uses an else branch
if (heightAlice > heightBob) {
    taller = heightAlice
} else {
    taller = heightBob
}

// Uses if as an expression
taller = if (heightAlice > heightBob) heightAlice else heightBob

// Uses else if as an expression:
val heightLimit = 150
val heightOrLimit = if (heightLimit > heightAlice) heightLimit else if (heightAlice > heightBob) heightAlice else heightBob

println("Taller height is $taller")
// Taller height is 175
println("Height or limit is $heightOrLimit")
// Height or limit is 175

在 if 表达式中,每个分支都可以是一个代码块,代码块中最后一个表达式的值会成为该分支的返回结果:

kotlin 复制代码
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 的 switch 语句比较相似。when 会计算它的参数,然后把结果和每个分支的条件进行比较,直到条件的结果和参数相符,比如:

kotlin 复制代码
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")
}
// User can edit content

在 Kotlin 中,when 有两种使用方式:作为表达式或作为语句。

  • 作为表达式时,when 会返回一个值,你可以在后续代码中使用这个值。
  • 作为语句时,when 仅执行特定操作,不会返回结果。

when 既可以带主体(subject) 使用,也可以不带主体使用。这两种方式的运行逻辑相同,但带主体的写法通常会让代码更易读、更易维护,因为它能清晰地表明你要检查的对象是什么。

当你需要覆盖所有可能性的时候,才考虑使用 when。这种覆盖所有可能情况的要求,在 Kotlin 中被称为 "穷尽性"(Exhaustiveness)。

语句

如果将 when 用作语句,那么不需要覆盖所有可能的情况。在这种情况下,即使某些情况未被覆盖,也不会触发任何分支,也不会产生错误:

kotlin 复制代码
val deliveryStatus = "OutForDelivery"
when (deliveryStatus) {
    // Not all cases are covered
    "Pending" -> print("Your order is being prepared")
    "Shipped" -> print("Your order is on the way")
}

就像 if 一样,每一个分支可以是一个代码块,并且代码块最后一行表达式的值就是分支的值。

表达式

如果用 when 作为一个表达式,必须覆盖所有的可能性。第一个匹对上的符合条件的分支,就是整个 when 的值。如果没有覆盖所有的可能性,那么编译器会报错。

如果 when 表达式带着主体,那么可以使用 else 分支来确保能覆盖所有的可能性。比如,假如你的主体是个布尔、枚举、密封类或者它们的可空类型,就可以在不使用else的情况下,来覆盖所有的可能性:

kotlin 复制代码
enum class Bit {
    ZERO, ONE
}

fun getRandomBit(): Bit {
    return if (Random.nextBoolean()) Bit.ONE else Bit.ZERO
}

fun main() {
    val numericValue = when (getRandomBit()) {
        // No else branch is needed because all cases are covered
        Bit.ZERO -> 0
        Bit.ONE -> 1
    }

    println("Random bit as number: $numericValue")
    // Random bit as number: 0

如果 when 表达式没有主体,那么必须有一个 else 分支。这样没有满足条件的分支时,else 分支就会执行:

kotlin 复制代码
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)
// Local and remote files are the same size

when 的其他用法

when 语句和表达式为简化代码、处理多条件、执行类型检查提供了多种不同的方式。

使用逗号将多条件合并到一个分支:

kotlin 复制代码
when (ticketPriority) {
    "Low", "Medium" -> print("Standard response time")
    else -> print("High-priority handling")
}

在 when 中,可以直接使用结果为布尔值(true/false)的表达式作为分支条件:

kotlin 复制代码
when (enteredPin) {
    // Expression
    storedPin.toInt() -> print("PIN is correct")
    else -> print("Incorrect PIN")
}

在 when 中,可以使用 in 关键字检查一个值是否包含在某个范围(range)或集合(collection)中,也可以使用 !in 检查值是否不包含在其中:

kotlin 复制代码
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 关键字来检查一个值的类型,由于智能转换,所以直接调用成员方法和属性:

kotlin 复制代码
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 判断链。不带主体,分支条件是一个简洁的布尔表达式。第一个满足条件的分支就执行:

kotlin 复制代码
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 is odd

最后,可以使用下面的语法将将when 主体(subject)捕获到一个变量中:

kotlin 复制代码
fun main() {
    val message = when (val input = "yes") {
        "yes" -> "You said yes"
        "no" -> "You said no"
        else -> "Unrecognized input: $input"
    }

    println(message)
    // You said yes
}

在 when 表达式或语句中声明的主体变量(即通过 when (val x = ...) 语法定义的变量),其作用域仅限于整个 when 的内部,在 when 外部无法访问该变量。

守护条件

守护条件允许在 when 表达式或语句的分支中包含多个条件,这就让复杂的控制流程更加清晰和简洁。只要 when 带有主体(subject),就可以使用守护条件。

when 的分支中,你可以将守护条件(guard condition) 放在主条件(primary condition) 之后,两者通过 if 连接。

kotlin 复制代码
fun feedAnimal(animal: Animal) {
    when (animal) {
        // Branch with only primary condition
        // Calls feedDog() when animal is Dog
        is Animal.Dog -> feedDog()
        // Branch with both primary and guard conditions
        // Calls feedCat() when animal is Cat and not mouseHunter
        is Animal.Cat if !animal.mouseHunter -> feedCat()
        // Prints "Unknown animal" if none of the above conditions match
        else -> println("Unknown animal")
    }
}

fun main() {
    val animals = listOf(
        Animal.Dog("Beagle"),
        Animal.Cat(mouseHunter = false),
        Animal.Cat(mouseHunter = true)
    )

    animals.forEach { feedAnimal(it) }
    // Feeding a dog
    // Feeding a cat
    // Unknown animal
}

多个条件使用逗号连接时,不能使用守护条件:

kotlin 复制代码
0, 1 -> print("x == 0 or x == 1")

在同一个 when 表达式或语句中,可以同时包含带守护条件的分支和不带守护条件的分支。带守护条件的分支只有在主条件和守护条件都为 true 时才会执行;而如果主条件不匹配,对应的守护条件根本不会被计算(短路特性)。

由于 when 语句不需要覆盖所有可能的情况,在不带 else 分支的 when 语句中使用守护条件时,如果没有任何条件匹配(包括主条件和守护条件都不满足的情况),则不会执行任何代码。

与 when 语句不同,when 表达式必须覆盖所有可能的情况。如果在 when 表达式中使用守护条件且不添加 else 分支,编译器会强制要求处理每一种可能的情况,以避免运行时错误。

可以在单个分支中使用布尔运算符 &&(与)或 ||(或)组合多个守护条件。为避免混淆,建议用括号将布尔表达式括起来:

kotlin 复制代码
when (animal) {
    is Animal.Cat if (!animal.mouseHunter && animal.hungry) -> feedCat()
}

守护条件同样支持 else if:

kotlin 复制代码
when (animal) {
    // Checks if `animal` is `Dog`
    is Animal.Dog -> feedDog()
    // Guard condition that checks if `animal` is `Cat` and not `mouseHunter`
    is Animal.Cat if !animal.mouseHunter -> feedCat()
    // Calls giveLettuce() if none of the above conditions match and animal.eatsPlants is true
    else if animal.eatsPlants -> giveLettuce()
    // Prints "Unknown animal" if none of the above conditions match
    else -> println("Unknown animal")
}

For 循环

使用For 循环来迭代集合、数组、区间:

kotlin 复制代码
for (item in collection) print(item)

循环体使用花括号包括{}:

kotlin 复制代码
println("Things to buy:")
for (item in shoppingList) {
    println("- $item")
}
// Things to buy:
// - Milk
// - Bananas
// - Bread

Ranges 区间

如果是迭代一个数字区间,那么可以使用区间表达式 .. 和 ..< 操作符:

kotlin 复制代码
println("Closed-ended range:")
for (i in 1..6) {
    print(i)
}
// Closed-ended range:
// 123456

println("\nOpen-ended range:")
for (i in 1..<6) {
    print(i)
}
// Open-ended range:
// 12345

println("\nReverse order in steps of 2:")
for (i in 6 downTo 0 step 2) {
    print(i)
}
// Reverse order in steps of 2:
// 6420

Arrays 数组

如果想要带着index迭代数组或者集合,可以使用indices属性:

kotlin 复制代码
for (i in routineSteps.indices) {
    println(routineSteps[i])
}
// Wake up
// Brush teeth
// Make coffee

并且,开发者也可以使用标准库中的.withIndex() 方法:

kotlin 复制代码
for ((index, value) in routineSteps.withIndex()) {
    println("The step at $index is \"$value\"")
}
// The step at 0 is "Wake up"
// The step at 1 is "Brush teeth"
// The step at 2 is "Make coffee"

Iterators 迭代器

for 循环可以迭代任何内置迭代器的对象。集合默认提供了迭代器,区间和数组会被编译为基于 index-based 的循环。

通过成员或者扩展方法调用iterator()来返回 Iterator<>,这样开发者可以自定义迭代器。iterator()必须包含:next() 和 hasNext();

继承 Iterable<T> 接口,实现iterator(), next(), 和 hasNext()方法,是自定义迭代器的最简单的方式:

koltin 复制代码
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")
    }
    // Reading page 1
    // Reading page 2
    // Reading page 3
}

While 循环

while 和 do-while 循环会在条件满足时,持续的运行代码。二者的不同在于条件的检查时机:

  • while 的流程是:检查条件,执行代码,检查条件...

  • do-while 的流程是:执行代码,检查条件。条件满足的话,继续执行。因此,代码至少会执行一次,无视条件的情况。

while 循环的写法:条件写在圆括号,代码写在花括号:

kotlin 复制代码
while (carsInGarage < maxCapacity) {
    println("Car entered. Cars now in garage: ${++carsInGarage}")
}
// Car entered. Cars now in garage: 1
// Car entered. Cars now in garage: 2
// Car entered. Cars now in garage: 3

println("Garage is full!")
// Garage is full!

do-while循环的写法:代码写在花括号,判断的条件写在圆括号:

kotlin 复制代码
do {
    roll = Random.nextInt(1, 7)
    println("Rolled a $roll")
} while (roll != 6)
// Rolled a 2
// Rolled a 6

println("Got a 6! Game over.")
// Got a 6! Game over.

二者只是执行顺序不一样。

原文

相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
ai2work2 天前
ch23 综合复刻:从零做一个最小可用版本(capstone)
kotlin
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
ai2work2 天前
ch21 签名、校验与发版
kotlin
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui