Kotlin 2.1.0 入门教程(十一)for、while、return、break、continue
for
循环
for
循环会遍历任何提供迭代器的对象。
for (item in collection) print(item)for (int: Int in ints) {println(int)
}
for
循环会遍历任何提供迭代器的对象,这意味着该对象必须满足以下条件:
-
具有一个成员函数或扩展函数
iterator()
,该函数返回一个Iterator<>
对象,而该对象:-
具有一个成员函数或扩展函数
next()
。 -
具有一个成员函数或扩展函数
hasNext()
,该函数返回一个Boolean
。
-
-
这三个函数都需要被标记为
operator
。
要遍历一个数字范围,可以使用范围表达式。
for (i in 1..3) {print(i)
}for (i in 6 downTo 0 step 2) {print(i)
}
对于范围或数组的 for
循环会被编译成基于索引的循环,而不会创建迭代器对象。
如果你想要通过索引遍历数组或列表,可以这样操作:
for (i in array.indices) {print(array[i])
}
或者,你可以使用 withIndex()
库函数。
for ((index, value) in array.withIndex()) {println("the element at $index is $value")
}
while
循环
while
和 do-while
循环会在条件满足的情况下持续执行其主体。
它们之间的区别在于条件检查的时机:
-
while
循环会先检查条件,如果条件满足,则执行主体,然后返回检查条件。 -
do-while
循环会先执行主体,然后才检查条件。如果条件满足,则循环重复执行。因此,do-while
的主体至少会执行一次,无论条件是否满足。
while (x > 0) {x--
}do {val y = retrieveData()
} while (y != null) // y 在这里是可以访问的。
在循环中,Kotlin
支持传统的 break
和 continue
操作符。
返回和跳转
Kotlin
提供了三种结构化跳转表达式:
-
return
:默认情况下,return
会从最近的封闭函数或匿名函数中返回。 -
break
:会终止最近的封闭循环。 -
continue
:会跳过当前循环的当前迭代,继续执行下一次迭代。
fun example() {for (i in 1..10) {if (i == 5) {return // 从函数 example 中返回。}println(i)}
}
fun example() {for (i in 1..10) {if (i == 5) {break // 终止最近的循环。}println(i)}
}
fun example() {for (i in 1..10) {if (i == 5) {continue // 跳过当前迭代,继续下一次迭代。}println(i)}
}
return
的作用范围:
-
在匿名函数(如
fun
声明的函数)中,return
会从该函数返回。 -
在
Lambda
表达式中,return
会从最近的封闭函数返回,而不是从Lambda
表达式返回。如果需要从Lambda
表达式返回,可以使用标签。
break
和 continue
的作用范围:
-
break
和continue
只能用于循环结构(如for
、while
、do-while
)。 -
如果需要从嵌套循环中跳出,可以使用标签。
如果需要从嵌套循环中跳出,或者从 Lambda
表达式中返回,可以使用标签。例如:
loop@ for (i in 1..3) {for (j in 1..3) {if (i == 2 && j == 2) {break@loop // 从带有标签的外层循环中跳出。}println("i = $i, j = $j")}
}
所有这些表达式都可以作为更大表达式的一部分使用:
val s = person.name ?: return
这些表达式的类型是 Nothing
类型。
Nothing
类型是 Kotlin
中的一个特殊类型,表示《无值》。它是一个不可实例化的类型,表示代码不会返回任何值,通常用于表示程序的退出点(如 return
、throw
等)。
在上面的例子中:
-
person.name
是一个可能为null
的表达式。 -
?:
是Kotlin
的空合并运算符(Elvis
运算符),用于提供一个默认值,当左侧表达式为null
时,返回右侧的值。 -
如果
person.name
不为null
,则s
将被赋值为person.name
。 -
如果
person.name
为null
,则执行return
,表示从当前函数返回。 -
由于
return
是一个跳转表达式,它不会返回任何值,因此它的类型是Nothing
。这意味着val s = person.name ?: return
这行代码在person.name
为null
时,会直接从函数返回,而不会继续执行后续代码。
带标签的 break
和 continue
在 Kotlin
中,任何表达式都可以被标记上一个标签。
标签的格式是一个标识符后跟一个 @
符号,例如 abc@
或 fooBar@
。
要给一个表达式加上标签,只需在它前面添加一个标签即可。
fun main() {/*i = 1, j = 11i = 1, j = 12i = 1, j = 13i = 1, j = 14i = 1, j = 15i = 2, j = 11i = 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")}}
}
fun main() {/*i = 1, j = 11i = 1, j = 12i = 1, j = 13i = 1, j = 14i = 1, j = 15i = 2, j = 11i = 2, j = 12i = 3, j = 11i = 3, j = 12i = 3, j = 13i = 3, j = 14i = 3, j = 15i = 4, j = 11i = 4, j = 12i = 4, j = 13i = 4, j = 14i = 4, j = 15i = 5, j = 11i = 5, j = 12i = 5, j = 13i = 5, j = 14i = 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
)使用 break
和 continue
。这种非本地的用法在嵌套的内联函数中使用的 Lambda
表达式中是有效的。
break
和 continue
通常只能在它们所在的循环中使用。然而,在某些特定的上下文中,它们可以跨越多个作用域进行跳转,这种行为被称为《非本地跳转》。
当一个 Lambda
表达式被传递给一个内联函数(inline
函数)时,break
和 continue
可以在 Lambda
表达式中使用,而它们的作用范围会扩展到内联函数的调用点。这意味着你可以从 Lambda
表达式中跳转到外层的循环。
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
。
fun main() {fun foo() {listOf(1, 2, 3, 4, 5).forEach lit@ {if (it == 3) return@litprint(it)}}foo() // 1245
}
现在,它只从 Lambda
表达式返回。
通常,使用隐式标签会更加方便,因为这种标签的名称与 Lambda
表达式所传递到的函数名称相同。
fun main() {fun foo() {listOf(1, 2, 3, 4, 5).forEach {if (it == 3) return@forEachprint(it)}}foo() // 1245
}
或者,你可以将 Lambda
表达式替换为匿名函数。在匿名函数中,return
语句将从匿名函数本身返回。
fun main() {fun foo() {listOf(1, 2, 3, 4, 5).forEach(fun (v: Int) {if (v == 3) returnprint(v)})}foo() // 1245
}
请注意,在前面的三个例子中,本地返回的使用与在普通循环中使用 continue
是类似的。
虽然没有直接等价于 break
的功能,但可以通过添加一个嵌套的 Lambda
表达式,并从其中非本地返回来模拟 break
的行为。
fun main() {// 12run loop@ {listOf(1, 2, 3, 4, 5).forEach {if (it == 3) return@loopprint(it)}}
}
fun main() {// 12run loop@ {listOf(1, 2, 3, 4, 5).forEach(fun (v: Int) {if (v == 3) return@loopprint(v)})}
}
当返回一个值时,解析器会优先考虑带标签的返回:
return@a 1
它是:在标签 @a
处返回值 1
。而不是:返回一个带标签的表达式 @a 1
。
在某些情况下,你可以在不使用标签的情况下从 Lambda
表达式返回。这种非本地返回位于 Lambda
表达式中,但会退出封闭的内联函数。