Kotlin之控制语句和表达式

原文链接 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错了某个标签,调试的难度是相当大的。更多的时候需要仔细想想有没有更好的遍历方式,而不是靠标签来救命。

原创不易,打赏点赞在看收藏分享 总要有一个吧

相关推荐
kyriewen7 分钟前
DOM树与节点操作:用JS给网页“动手术”
前端·javascript·面试
郝学胜-神的一滴13 分钟前
【技术实战】500G单行大文件读取难题破解!生成器+自定义函数最优方案解析
开发语言·python·程序人生·面试
晴栀ay31 分钟前
Generator + RxJS 重构 LLM 流式输出的“丝滑”架构
javascript·后端·llm
下次一定x35 分钟前
深度解析 Kratos 客户端服务发现与负载均衡:从 Dial 入口到 gRPC 全链路落地(下篇)
后端·go
软件测试媛1 小时前
软件测试面试题个人总结
功能测试·面试·ai软件测试
客卿1231 小时前
用两个栈实现队列
android·java·开发语言
彭于晏Yan2 小时前
SpringBoot整合ECC实现文件签名与验签
java·spring boot·后端
studyForMokey2 小时前
【Android面试】Gradle专题
android·面试·职场和发展
pupudawang2 小时前
Spring EL 表达式的简单介绍和使用
java·后端·spring
深蓝轨迹2 小时前
Redis 消息队列
java·数据库·redis·缓存·面试·秒杀