Kotlin 2.1.0 入门教程(十一)for、while、return、break、continue

for 循环

for 循环会遍历任何提供迭代器的对象。

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

for (int: Int in ints) {
    println(int)
}

for 循环会遍历任何提供迭代器的对象,这意味着该对象必须满足以下条件:

  • 具有一个成员函数或扩展函数 iterator(),该函数返回一个 Iterator<> 对象,而该对象:

    • 具有一个成员函数或扩展函数 next()

    • 具有一个成员函数或扩展函数 hasNext(),该函数返回一个 Boolean

  • 这三个函数都需要被标记为 operator

要遍历一个数字范围,可以使用范围表达式。

kotlin 复制代码
for (i in 1..3) {
    print(i)
}

for (i in 6 downTo 0 step 2) {
    print(i)
}

对于范围或数组的 for 循环会被编译成基于索引的循环,而不会创建迭代器对象。

如果你想要通过索引遍历数组或列表,可以这样操作:

kotlin 复制代码
for (i in array.indices) {
    print(array[i])
}

或者,你可以使用 withIndex() 库函数。

kotlin 复制代码
for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}

while 循环

whiledo-while 循环会在条件满足的情况下持续执行其主体。

它们之间的区别在于条件检查的时机:

  • while 循环会先检查条件,如果条件满足,则执行主体,然后返回检查条件。

  • do-while 循环会先执行主体,然后才检查条件。如果条件满足,则循环重复执行。因此,do-while 的主体至少会执行一次,无论条件是否满足。

kotlin 复制代码
while (x > 0) {
    x--
}

do {
    val y = retrieveData()
} while (y != null) // y 在这里是可以访问的。

在循环中,Kotlin 支持传统的 breakcontinue 操作符。

返回和跳转

Kotlin 提供了三种结构化跳转表达式:

  • return:默认情况下,return 会从最近的封闭函数或匿名函数中返回。

  • break:会终止最近的封闭循环。

  • continue:会跳过当前循环的当前迭代,继续执行下一次迭代。

kotlin 复制代码
fun example() {
    for (i in 1..10) {
        if (i == 5) {
            return // 从函数 example 中返回。
        }
        println(i)
    }
}
kotlin 复制代码
fun example() {
    for (i in 1..10) {
        if (i == 5) {
            break // 终止最近的循环。
        }
        println(i)
    }
}
kotlin 复制代码
fun example() {
    for (i in 1..10) {
        if (i == 5) {
            continue // 跳过当前迭代,继续下一次迭代。
        }
        println(i)
    }
}

return 的作用范围:

  • 在匿名函数(如 fun 声明的函数)中,return 会从该函数返回。

  • Lambda 表达式中,return 会从最近的封闭函数返回,而不是从 Lambda 表达式返回。如果需要从 Lambda 表达式返回,可以使用标签。

breakcontinue 的作用范围:

  • breakcontinue 只能用于循环结构(如 forwhiledo-while)。

  • 如果需要从嵌套循环中跳出,可以使用标签。

如果需要从嵌套循环中跳出,或者从 Lambda 表达式中返回,可以使用标签。例如:

kotlin 复制代码
loop@ for (i in 1..3) {
    for (j in 1..3) {
        if (i == 2 && j == 2) {
            break@loop // 从带有标签的外层循环中跳出。
        }
        println("i = $i, j = $j")
    }
}

所有这些表达式都可以作为更大表达式的一部分使用:

kotlin 复制代码
val s = person.name ?: return

这些表达式的类型是 Nothing 类型。

Nothing 类型是 Kotlin 中的一个特殊类型,表示《无值》。它是一个不可实例化的类型,表示代码不会返回任何值,通常用于表示程序的退出点(如 returnthrow 等)。

在上面的例子中:

  • person.name 是一个可能为 null 的表达式。

  • ?:Kotlin 的空合并运算符(Elvis 运算符),用于提供一个默认值,当左侧表达式为 null 时,返回右侧的值。

  • 如果 person.name 不为 null,则 s 将被赋值为 person.name

  • 如果 person.namenull,则执行 return,表示从当前函数返回。

  • 由于 return 是一个跳转表达式,它不会返回任何值,因此它的类型是 Nothing。这意味着 val s = person.name ?: return 这行代码在 person.namenull 时,会直接从函数返回,而不会继续执行后续代码。

带标签的 breakcontinue

Kotlin 中,任何表达式都可以被标记上一个标签。

标签的格式是一个标识符后跟一个 @ 符号,例如 abc@fooBar@

要给一个表达式加上标签,只需在它前面添加一个标签即可。

kotlin 复制代码
fun main() {
    /*
    i = 1, j = 11
	i = 1, j = 12
	i = 1, j = 13
	i = 1, j = 14
	i = 1, j = 15
	i = 2, j = 11
	i = 2, j = 12
    */
    loop@ for (i in 1..5) {
        for (j in 11..15) {
            if (i == 2 && j == 13) {
                break@loop
            }
            println("i = $i, j = $j")
        }
    }
}
kotlin 复制代码
fun main() {
    /*
    i = 1, j = 11
    i = 1, j = 12
    i = 1, j = 13
    i = 1, j = 14
    i = 1, j = 15
    i = 2, j = 11
    i = 2, j = 12
    i = 3, j = 11
    i = 3, j = 12
    i = 3, j = 13
    i = 3, j = 14
    i = 3, j = 15
    i = 4, j = 11
    i = 4, j = 12
    i = 4, j = 13
    i = 4, j = 14
    i = 4, j = 15
    i = 5, j = 11
    i = 5, j = 12
    i = 5, j = 13
    i = 5, j = 14
    i = 5, j = 15
    */
    loop@ for (i in 1..5) {
        for (j in 11..15) {
            if (i == 2 && j == 13) {
                continue@loop
            }
            println("i = $i, j = $j")
        }
    }
}

在某些情况下,你可以在没有显式定义标签的情况下,非本地地(non-locally)使用 breakcontinue。这种非本地的用法在嵌套的内联函数中使用的 Lambda 表达式中是有效的。

breakcontinue 通常只能在它们所在的循环中使用。然而,在某些特定的上下文中,它们可以跨越多个作用域进行跳转,这种行为被称为《非本地跳转》。

当一个 Lambda 表达式被传递给一个内联函数(inline 函数)时,breakcontinue 可以在 Lambda 表达式中使用,而它们的作用范围会扩展到内联函数的调用点。这意味着你可以从 Lambda 表达式中跳转到外层的循环。

kotlin 复制代码
inline fun processList(list: List<Int>, action: (Int) -> Unit) {
    for (item in list) {
        action(item)
    }
}

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)
    processList(numbers) { number ->
        if (number == 3) {
            break // 非本地 break,跳出 processList 的循环。
        }
        println(number)
    }
}

这种非本地跳转允许你从 Lambda 表达式中直接跳出外层的循环。

只有在内联函数中使用的 Lambda 表达式才支持非本地跳转。这是因为内联函数会将 Lambda 表达式直接插入到调用点,从而允许跳转到外层的作用域。

带标签的 return

函数可以通过函数字面量、局部函数和对象表达式进行嵌套。带标签的 return 允许你从外层函数返回。

最重要的用例是从 Lambda 表达式中返回。要从 Lambda 表达式返回,需要给 Lambda 表达式加上标签,并使用带标签的 return

kotlin 复制代码
fun main() {
    fun foo() {
        listOf(1, 2, 3, 4, 5).forEach lit@ {
            if (it == 3) return@lit
            print(it)
        }
	}
    foo() // 1245
}

现在,它只从 Lambda 表达式返回。

通常,使用隐式标签会更加方便,因为这种标签的名称与 Lambda 表达式所传递到的函数名称相同。

kotlin 复制代码
fun main() {
    fun foo() {
        listOf(1, 2, 3, 4, 5).forEach {
            if (it == 3) return@forEach
            print(it)
        }
	}
    foo() // 1245
}

或者,你可以将 Lambda 表达式替换为匿名函数。在匿名函数中,return 语句将从匿名函数本身返回。

kotlin 复制代码
fun main() {
    fun foo() {
        listOf(1, 2, 3, 4, 5).forEach(fun (v: Int) {
            if (v == 3) return
            print(v)
        })
	}
    foo() // 1245
}

请注意,在前面的三个例子中,本地返回的使用与在普通循环中使用 continue 是类似的。

虽然没有直接等价于 break 的功能,但可以通过添加一个嵌套的 Lambda 表达式,并从其中非本地返回来模拟 break 的行为。

kotlin 复制代码
fun main() {
    // 12
    run loop@ {
        listOf(1, 2, 3, 4, 5).forEach {
            if (it == 3) return@loop
            print(it)
        }
	}
}
kotlin 复制代码
fun main() {
    // 12
    run loop@ {
        listOf(1, 2, 3, 4, 5).forEach(fun (v: Int) {
            if (v == 3) return@loop
            print(v)
        })
	}
}

当返回一个值时,解析器会优先考虑带标签的返回:

kotlin 复制代码
return@a 1

它是:在标签 @a 处返回值 1。而不是:返回一个带标签的表达式 @a 1

在某些情况下,你可以在不使用标签的情况下从 Lambda 表达式返回。这种非本地返回位于 Lambda 表达式中,但会退出封闭的内联函数。

相关推荐
货拉拉技术2 小时前
记一次无障碍测试引发app崩溃问题的排查与解决
android·前端·程序员
GrimRaider3 小时前
【逆向工程】破解unity的安卓apk包
android·unity·游戏引擎·软件逆向
yzpyzp3 小时前
Jetpack之ViewBinding和DataBinding的区别
android
小墙程序员3 小时前
一文了解Android的build目录结构
android·gradle
叶落方知秋4 小时前
OkHttp 3.10.0版本源码之重试重定向拦截器原理分析
android·前端框架
无聊的烤苕皮4 小时前
MySQL第五次作业(触发器、存储过程)
android·mysql·adb
ianozo5 小时前
BUU34 [BSidesCF 2020]Had a bad day1 【php://filter】
android
xvch5 小时前
Kotlin 2.1.0 入门教程(十三)异常、Nothing
android·kotlin
深色風信子5 小时前
Kotlin Bytedeco OpenCV 图像图像51.1 KNN背景消除
opencv·kotlin·bytedeco·knn背景消除·背景消除
陈老师还在写代码5 小时前
安卓开发用Java、Flutter、Kotlin的区别
android·java·flutter