原文链接 Kotlin Controls and Expressions
有结果返回的是表达式,没有返回的称之为语句,语句最大的问题是它没有返回值,那么想要保存结果就必然会产生副作用,比如改变变量。很多时候这是不够方便的,并且在多线程条件下,这甚至是不安全的。Kotlin中,为了加强线程安全性和方便并发和异步,因此绝大多数语句都是表达式。
分支表达式
Kotlin中没有三元条件符(a > b ? a : b),但它的条件分支都是表达式,可以直接放在赋值符的右边,或者用在return语句中。
if表达式
它是一个两个分支的表达式,是有返回值的:
kotlin
val maxV = if (a > b) a else b
当然了,把它当作常规的语句来使用也是没有问题的:
kotlin
var max: Int
if (a > b) {
max = a
} else {
max = b
}
when表达式
当超过2个分支时,if就不能用了,这时可以用when表达式,它支持多个分支,类似于其他语言中的switch:
kotlin
when (x) {
1 -> println("it is 1")
2 -> println("it is 2")
else -> {
println("it is neight 1 nor 2")
}
}
需要注意的是,每一行是一个条件,并不是单单指参数与其相等,比如:
kotlin
when (x) {
in 1..5 -> println("Less than 5 bigger than 1")
x.isEven() -> println("it is even")
else -> println("It is neither even or less than 5")
}
当然,最重要的是when是一个表达式,可以直接用在赋值符的右边,或者当参数传,或者用在return中:
kotlin
fun Request.getBody() =
when (val response = executeRequest()) {
is Success -> response.body
is HttpError -> throw HttpException(response.status)
}
这里的when就是函数的返回值,可以看到when是一个表达式,它会返回一个值,这个值直接作为函数的返回值。
从这几个示例可以看出when表达式相当强大比其他语言的switch要强大许多,并且可以直接当作返回值,当需要超过2个条件分支时就可以使用when表达式。
循环语句
循环是语句,与其他语言也差不多。
while loop
kotlin
while (x < 10) {
println(x)
x++
}
屁股向后式do-while loop
kotlin
do {
x = poll()
} while (x < 10)
强大的for loop
这个是最强大,也是最常用的循环语句遍历数组,集合和固定步长时的首选。
kotlin
for (item in collection) print(item)
这里的collection可以是数组和集合(列表和Set)。严格来说只要collection类型实现了iterator()和next(),就可以在for loop中使用。
for加上range,可以非常强大:
kotlin
for (i in 1..10) // = for (int i = 1; i <= 10; i++)
for (i in 0 until 10) // = for (int i = 0; i < 10; i++)
for (i in 9 downTo 0) // = for (int i = 9; i >= 0; i--)
for (i in 0 until 10 step 2) // = for (int i = 0; i < 10; i += 2)
如果是数组或者列表,但又必须要用索引,也可以直接来:
kotlin
for (i in array.indices) {
println(array[i]) // 'i' is the index
}
其实有更好的方式:
kotlin
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}
其实吧,Kotlin是多范式编程语言,天生支持函数式编程,多数情况下不建议直接上for loop,而是用函数式方式的forEach,数组和集合都支持forEach的:
kotlin
array.forEach { println(it) }
终止语句
当想提前退出函数的执行,或者循环时,就需要用到终止语句,有三种return, break和continue。
return终止函数执行
这个都比较熟悉,常规的用法都是一样的,可以提前退出函数:
kotlin
fun plot(x: Int) {
if (x < 1) {
return -1
}
...
return y
}
但当有嵌套的lambda时,如不特别指定,return会退出外层的函数,而不是像想当然的退出lambda,比如:
kotlin
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return // non-local return directly to the caller of foo()
print(it)
}
println("this point is unreachable")
}
这个不是终止lambda的执行,而是直接退出函数foo的执行。如果想解决呢,即也退出遍历的lambda有三种方案:
- 使用标签
kotlin
fun foo() {
listOf(1, 2, 3, 4, 5).forEach lit@{
if (it == 3) return@lit // local return to the caller of the lambda - the forEach loop
print(it)
}
print(" done with explicit label")
}
- 使用隐式标签,也即遍历的方法当作标签
kotlin
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@forEach // local return to the caller of the lambda - the forEach loop
print(it)
}
print(" done with implicit label")
}
- 使用匿名函数而不是lambda,匿名函数与常规函数体效力一样,所以return只在函数体内生效
kotlin
fun foo() {
listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
if (value == 3) return // local return to the caller of the anonymous function - the forEach loop
print(value)
})
print(" done with anonymous function")
}
这三种方式,如果非要使用,建议使用方式二,用自带的隐式label,因为比较方便,可读性也不差。
但,非常不建议如此使用return语句 ,这本是应该避免的问题,lambda多半是用在函数式遍历和处理,在lambda里面提加return本就是非常奇怪的事情。因为如果某些条件不满足,想不执行此lambda,应该用filter啊,而不是笨拙的非要在lambda中去终止:
kotlin
fun foo() {
listOf(1, 2, 3, 4, 5)
.filter(i -> i != 3)
.forEach { println(it) }
print("You can do whatever you like here.")
}
循环的终止
break终止当前循环,continue则是跳过当前循环的当前步骤,直接跳到下一次迭代。这两个的常规使用与其他语言是一样的。
但对于break,一般来说有一个痛点,就是当有循环嵌套时,break只能终止一层,如果想终止所有循环时,只能再手动的加条件去判断,然后再一层一层的break,比如:
kotlin
for (i in 0 until 10) {
var found = false
for (j in i until 10) {
if (array[i] + array[j] == target) {
found = true
break // only break inner for loop
}
}
if (found) {
break // this break outer for loop
}
}
这多少有点笨拙和丑陋,Kotlin有更优雅的解决方式,就是引入了标签label,可以给循环加上标签,在break时可以指定标签,同样是上面的情况,可以这样做:
kotlin
loop@ for (i in 0 until 10) {
for (j in i until 10) {
if (array[i] + array[j] == target) {
break @loop // break all loops easily
}
}
}
其实吧,这玩意儿跟当年的goto是一样的,虽然可行,但不建议多使用 ,标签多了以后会让程序的执行更加的混乱,试想假如在层层循环中break错了某个标签,调试的难度是相当大的。更多的时候需要仔细想想有没有更好的遍历方式,而不是靠标签来救命。