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.

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

原文

相关推荐
你听得到112 分钟前
弹窗库1.1.0版本发布!不止于统一,更是全面的体验升级!
android·前端·flutter
RainyJiang38 分钟前
布局与测量性能优化:让Compose从"嵌套地狱"到"扁平化管理"
android·android jetpack
dora1 小时前
DoraFund 2.0 集成与支付教程
android·区块链·github
用户093 小时前
将挂起函数或流转换为回调
android
枯骨成佛17 小时前
MTK Android 14 通过属性控制系统设置显示双栏或者单栏
android
雨白17 小时前
Android 自定义 View:范围裁切和几何变换
android
jiushiapwojdap18 小时前
Flutter上手记:为什么我的按钮能同时在iOS和Android上跳舞?[特殊字符][特殊字符]
android·其他·flutter·ios
limuyang221 小时前
Android RenderScript-toolkit库,替换老式的脚本方式(常用于高斯模糊)
android
柿蒂21 小时前
产品需求驱动下的技术演进:动态缩放View的不同方案
android·kotlin·android jetpack