5. 循环控制:for循环、while循环、do-while循环、break与continue、标签与返回

循环,说白了就是让代码反复干同一件事。你想想看,如果让你打印1到100,你总不会写100行println吧?循环就是干这个的。

Kotlin的循环设计得挺讲究,既有传统的while,也有更现代的for-in。我个人习惯是:能用for的地方绝不用while,因为for更安全、更不容易写出死循环。

5.1 for循环:遍历的利器

Kotlin的for循环和Java差别很大。它没有传统的for (int i = 0; i < 10; i++)这种写法,而是用for (item in collection)的形式。我第一次从Java转过来时还有点不习惯,但用了一周就真香了。

// 遍历区间
for (i in 1..5) {
    println(i)  // 输出 1 2 3 4 5
}

// 遍历数组
val arr = intArrayOf(10, 20, 30)
for (item in arr) {
    println(item)
}

// 带索引遍历
for ((index, value) in arr.withIndex()) {
    println("arr[$index] = $value")
}

这里有个细节:1..5是闭区间,包含5。如果你不想包含结尾,用1 until 5,那就是1到4。

我的习惯:遍历集合时,我几乎只用for-in。它比while更简洁,而且不会出现索引越界的问题。我在项目中遇到过一位同事用while遍历列表,结果删元素时索引没处理好,直接崩了。

5.2 while循环:条件驱动

while循环和Java完全一样。当条件为true时,反复执行代码块。什么时候用while?当你不知道要循环多少次的时候。比如读取文件直到末尾,或者等待某个状态变化。

var x = 10
while (x > 0) {
    println(x)
    x--
}

// 读取输入直到输入"exit"
var input: String?
while (readLine().also { input = it } != "exit") {
    println("你输入了: $input")
}
注意:while循环最容易出死循环。我曾经在写网络重试逻辑时,忘记更新条件变量,结果程序卡死了整个服务。所以用while时,一定确保条件最终会变成false。

5.3 do-while循环:至少执行一次

do-while和while的区别就一句话:do-while至少执行一次,然后再判断条件。适合那些「不管怎样先干一次再说」的场景。

var count = 5
do {
    println("还剩 $count 次")
    count--
} while (count > 0)

// 一个实际场景:先发送请求,如果失败则重试
var retry = 3
do {
    println("尝试发送请求...")
    // 模拟请求
    retry--
} while (retry > 0)

嗯,这里要注意:do-while用得不多,但用对地方很顺手。比如用户输入验证,先让用户输入一次,不对再提示重新输入。

5.4 break与continue:控制循环节奏

break是「到此为止,全退出」,continue是「这次算了,继续下一次」。这两个关键字在任何语言里都差不多,但Kotlin里有个小增强——break和continue可以配合标签使用。

// break 示例:找到第一个偶数就停
for (i in 1..10) {
    if (i % 2 == 0) {
        println("找到偶数: $i")
        break
    }
}

// continue 示例:跳过奇数
for (i in 1..10) {
    if (i % 2 != 0) continue
    println(i)  // 只输出偶数
}
核心区别:break跳出整个循环,continue跳过当前迭代的剩余代码,直接进入下一次。

5.5 标签与返回:跳出多层循环

这是Kotlin比较有特色的地方。当你嵌套循环时,break和continue默认只作用于最内层循环。如果你想跳出外层循环,就需要标签。

outer@ for (i in 1..3) {
    for (j in 1..3) {
        if (i == 2 && j == 2) {
            println("跳出外层循环")
            break@outer
        }
        println("i=$i, j=$j")
    }
}

// 输出:
// i=1, j=1
// i=1, j=2
// i=1, j=3
// i=2, j=1
// 跳出外层循环

标签的语法很简单:在循环前面写标签名@,然后在break或continue后面加上@标签名。我个人觉得这个设计比Java的「标记块+break」要直观得多。

避坑指南:我曾经在写矩阵遍历时,嵌套了三层循环,忘了加标签,结果break只跳出了最内层,导致逻辑全乱套。后来我养成了一个习惯:只要嵌套超过两层,就提前把标签写好,哪怕暂时用不上。

5.6 循环中的return:不只是跳出循环

在Kotlin中,return在普通循环里会直接退出整个函数。这和Java一样。但如果你在lambda表达式中使用return,情况就不同了——它默认是「非局部返回」,也就是退出外层函数。

fun findFirstEven(numbers: List<Int>): Int? {
    for (n in numbers) {
        if (n % 2 == 0) return n  // 直接退出函数
    }
    return null
}

// lambda中的return
fun processList(list: List<Int>) {
    list.forEach {
        if (it == 0) return  // 这会退出processList函数,不是只退出forEach
        println(it)
    }
    println("这行可能不会执行")
}

如果你只想退出lambda而不是外层函数,用return@forEach,这其实就是标签的另一种形式。

5.7 知识体系总览

下面这张图把Kotlin循环控制的核心知识点串起来了。我建议你把它当作复习地图,每次写循环前扫一眼,能帮你选对工具。

Kotlin 循环控制知识体系 循环控制 循环类型 for-in 循环 while 循环 do-while 循环 控制语句 break 跳出循环 continue 跳过本次 return 退出函数 高级特性 标签 @ 跳转 嵌套循环控制 lambda 非局部返回 选择原则:能用for不用while,嵌套多层加标签

5.8 实战对比:什么时候用哪个?

场景 推荐循环 原因
遍历数组/列表 for-in 最简洁,不会越界
遍历区间数字 for-in + 区间 语法直观,支持步长
条件未知的重复 while 灵活,条件可动态变化
至少执行一次 do-while 保证首次执行
嵌套循环提前退出 for + 标签 精准控制跳出层级
集合操作(过滤/映射) forEach + lambda 函数式风格,更安全
重要提醒:不要在forEach的lambda里使用return(除非你明确知道它是非局部返回)。我见过太多人在这里踩坑——本来只想跳过当前元素,结果整个函数都退出了。用return@forEach才是正确的做法。

好了,循环控制这块就这些。说白了就是三件事:选对循环类型、用好控制语句、记住标签的威力。写代码时多想想「这个场景用哪个最合适」,慢慢就会形成肌肉记忆。


公众号:蓝海资料掘金营,微信deep3321