3.5 流程控制语句

流程控制语句是编程语言中的核心之一。可分为:

分支语句(ifwhen)
循环语句(forwhile )和
跳转语句 (returnbreakcontinuethrow)等。

3.5.1 if表达式

if-else语句是控制程序流程的最基本的形式,其中else是可选的。

在 Kotlin 中,if 是一个表达式,即它会返回一个值(跟Scala一样)。

代码示例:

  1. package com.easy.kotlin
  2. fun main(args: Array<String>) {
  3. println(max(1, 2))
  4. }
  5. fun max(a: Int, b: Int): Int {
  6. // 作为表达式
  7. val max = if (a > b) a else b
  8. return max // return if (a > b) a else b
  9. }
  10. fun max1(a: Int, b: Int): Int {
  11. // 传统用法
  12. var max1 = a
  13. if (a < b) max1 = b
  14. return max1
  15. }
  16. fun max2(a: Int, b: Int): Int {
  17. // With else
  18. var max2: Int
  19. if (a > b) {
  20. max2 = a
  21. } else {
  22. max2 = b
  23. }
  24. return max2
  25. }

另外,if 的分支可以是代码块,最后的表达式作为该块的值:

  1. fun max3(a: Int, b: Int): Int {
  2. val max = if (a > b) {
  3. print("Max is a")
  4. a
  5. } else {
  6. print("Max is b")
  7. b
  8. }
  9. return max
  10. }

if作为代码块时,最后一行为其返回值。

另外,在Kotlin中没有类似true? 1: 0这样的三元表达式。对应的写法是使用if else语句:

  1. if(true) 1 else 0

如果 if 表达式只有一个分支, 或者分支的结果是 Unit , 它的值就是 Unit 。

示例代码

  1. >>> val x = if(1==1) true
  2. >>> x
  3. kotlin.Unit
  4. >>> val y = if(1==1) true else false
  5. >>> y
  6. true

if-else语句规则:

  • if后的括号不能省略,括号里表达式的值须是布尔型

代码反例:

  1. >>> if("a") 1
  2. error: type mismatch: inferred type is String but Boolean was expected
  3. if("a") 1
  4. ^
  5. >>> if(1) println(1)
  6. error: the integer literal does not conform to the expected type Boolean
  7. if(1)
  8. ^
  • 如果条件体内只有一条语句需要执行,那么if后面的大括号可以省略。良好的编程风格建议加上大括号。
  1. >>> if(true) println(1) else println(0)
  2. 1
  3. >>> if(true) { println(1)} else{ println(0)}
  4. 1
  • 对于给定的if,else语句是可选的,else if 语句也是可选的。
  • else和else if同时出现时,else必须出现在else if 之后。
  • 如果有多条else if语句同时出现,那么如果有一条else if语句的表达式测试成功,那么会忽略掉其他所有else if和else分支。
  • 如果出现多个if,只有一个else的情形,else子句归属于最内层的if语句。

以上规则跟Java、C语言基本相同。

3.5.2 when表达式

when表达式类似于 switch-case 表达式。when会对所有的分支进行检查直到有一个条件满足。但相比switch而言,when语句要更加的强大,灵活。

Kotlin的极简语法表达风格,使得我们对分支检查的代码写起来更加简单直接:

  1. fun cases(obj: Any) {
  2. when (obj) {
  3. 1 -> print("第一项")
  4. "hello" -> print("这个是字符串hello")
  5. is Long -> print("这是一个Long类型数据")
  6. !is String -> print("这不是String类型的数据")
  7. else -> print("else类似于Java中的default")
  8. }
  9. }

像 if 一样,when 的每一个分支也可以是一个代码块,它的值是块中最后的表达式的值。

如果其他分支都不满足条件会到 else 分支(类似default)。

如果我们有很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:

  1. fun switch(x: Any) {
  2. when (x) {
  3. -1, 0 -> print("x == -1 or x == 0")
  4. 1 -> print("x == 1")
  5. 2 -> print("x == 2")
  6. else -> { // 注意这个块
  7. print("x is neither 1 nor 2")
  8. }
  9. }
  10. }

我们可以用任意表达式(而不只是常量)作为分支条件

  1. fun switch(x: Int) {
  2. val s = "123"
  3. when (x) {
  4. -1, 0 -> print("x == -1 or x == 0")
  5. 1 -> print("x == 1")
  6. 2 -> print("x == 2")
  7. 8 -> print("x is 8")
  8. parseInt(s) -> println("x is 123")
  9. else -> { // 注意这个块
  10. print("x is neither 1 nor 2")
  11. }
  12. }
  13. }

我们也可以检测一个值在 in 或者不在 !in 一个区间或者集合中:

  1. val x = 1
  2. val validNumbers = arrayOf(1, 2, 3)
  3. when (x) {
  4. in 1..10 -> print("x is in the range")
  5. in validNumbers -> print("x is valid")
  6. !in 10..20 -> print("x is outside the range")
  7. else -> print("none of the above")
  8. }

3.5.3 for循环

Kotlin的for循环跟现代的程序设计语言基本相同。

for 循环可以对任何提供迭代器(iterator)的对象进行遍历,语法如下:

  1. for (item in collection) {
  2. print(item)
  3. }

循环体可以是一个代码块。

  1. for (i in intArray) {
  2. ...
  3. }

代码示例

  1. /**
  2. * For loop iterates through anything that provides an iterator.
  3. * See http://kotlinlang.org/docs/reference/control-flow.html#for-loops
  4. */
  5. fun main(args: Array<String>) {
  6. for (arg in args)
  7. println(arg)
  8. // or
  9. println()
  10. for (i in args.indices)
  11. println(args[i])
  12. }

如果你想要通过索引遍历一个数组或者一个 list,你可以这么做:

  1. for (i in array.indices) {
  2. print(array[i])
  3. }

或者你可以用库函数 withIndex

  1. for ((index, value) in array.withIndex()) {
  2. println("the element at $index is $value")
  3. }

3.5.4 while循环

while 和 do .. while使用方式跟C、Java语言基本一致。

代码示例

  1. package com.easy.kotlin
  2. fun main(args: Array<String>) {
  3. var x = 10
  4. while (x > 0) {
  5. x--
  6. println(x)
  7. }
  8. var y = 10
  9. do {
  10. y = y + 1
  11. println(y)
  12. } while (y < 20) // y的作用域包含此处
  13. }

3.5.5 break 和 continue

breakcontinue都是用来控制循环结构的,主要是用来停止循环(中断跳转)。

1.break

我们在写代码的时候,经常会遇到在某种条件出现的时候,就直接提前终止循环。而不是等到循环条件为false时才终止。这个时候,我们就可以使用break结束循环。break用于完全结束一个循环,直接跳出循环体,然后执行循环后面的语句。

问题场景:打印数字1~10,只要遇到偶数,就结束打印。

代码示例:

  1. fun breakDemo_1() {
  2. for (i in 1..10) {
  3. println(i)
  4. if (i % 2 == 0) {
  5. break
  6. }
  7. } // break to here
  8. }

测试代码:

  1. @Test
  2. fun testBreakDemo_1(){
  3. breakDemo_1()
  4. }

输出:

  1. 1
  2. 2

2.continue

continue是只终止本轮循环,但是还会继续下一轮循环。可以简单理解为,直接在当前语句处中断,跳转到循环入口,执行下一轮循环。而break则是完全终止循环,跳转到循环出口。

问题场景:打印数字0~10,但是不打印偶数。

代码示例:

  1. fun continueDemo() {
  2. for (i in 1..10) {
  3. if (i % 2 == 0) {
  4. continue
  5. }
  6. println(i)
  7. }
  8. }

测试代码

  1. @Test
  2. fun testContinueDemo() {
  3. continueDemo()
  4. }

输出

  1. 1
  2. 3
  3. 5
  4. 7
  5. 9

3.5.6 return返回

在Java、C语言中,return语句使我们再常见不过的了。虽然在Scala,Groovy这样的语言中,函数的返回值可以不需要显示用return来指定,但是我们仍然认为,使用return的编码风格更加容易阅读理解。

在Kotlin中,除了表达式的值,有返回值的函数都要求显式使用return来返回其值。

代码示例

  1. fun sum(a: Int,b: Int): Int{
  2. return a+b
  3. }
  4. fun max(a: Int, b: Int): Int { if (a > b) return a else return b}

我们在Kotlin中,可以直接使用=符号来直接返回一个函数的值。

代码示例

  1. >>> fun sum(a: Int,b: Int) = a + b
  2. >>> fun max(a: Int, b: Int) = if (a > b) a else b
  3. >>> sum(1,10)
  4. 11
  5. >>> max(1,2)
  6. 2
  7. >>> val sum=fun(a:Int, b:Int) = a+b
  8. >>> sum
  9. (kotlin.Int, kotlin.Int) -> kotlin.Int
  10. >>> sum(1,1)
  11. 2
  12. >>> val sumf = fun(a:Int, b:Int) = {a+b}
  13. >>> sumf
  14. (kotlin.Int, kotlin.Int) -> () -> kotlin.Int
  15. >>> sumf(1,1)
  16. () -> kotlin.Int
  17. >>> sumf(1,1).invoke()
  18. 2

上述代码示例中,我们可以看到,后面的函数体语句有没有大括号 {} 意思完全不同。加了大括号,意义就完全不一样了。我们再通过下面的代码示例清晰的看出:

  1. >>> fun sumf(a:Int,b:Int) = {a+b}
  2. >>> sumf(1,1)
  3. () -> kotlin.Int
  4. >>> sumf(1,1).invoke
  5. error: function invocation 'invoke()' expected
  6. sumf(1,1).invoke
  7. ^
  8. >>> sumf(1,1).invoke()
  9. 2
  10. >>> fun maxf(a:Int, b:Int) = {if(a>b) a else b}
  11. >>> maxf(1,2)
  12. () -> kotlin.Int
  13. >>> maxf(1,2).invoke()
  14. 2

可以看出,sumfmaxf的返回值是函数类型:

  1. () -> kotlin.Int
  2. () -> kotlin.Int

这点跟Scala是不同的。在Scala中,带不带大括号{},意思一样:

  1. scala> def maxf(x:Int, y:Int) = { if(x>y) x else y }
  2. maxf: (x: Int, y: Int)Int
  3. scala> def maxv(x:Int, y:Int) = if(x>y) x else y
  4. maxv: (x: Int, y: Int)Int
  5. scala> maxf(1,2)
  6. res4: Int = 2
  7. scala> maxv(1,2)
  8. res6: Int = 2

我们可以看出maxf: (x: Int, y: Int)Intmaxv: (x: Int, y: Int)Int签名是一样的。在这里,Kotlin跟Scala在大括号的使用上,是完全不同的。

然后,调用方式是直接调用invoke()函数。通过REPL的编译错误提示信息,我们也可以看出,在Kotlin中,调用无参函数也是要加上括号()的。

kotlin 中 return 语句会从最近的函数或匿名函数中返回,但是在Lambda表达式中遇到return,则直接返回最近的外层函数。例如下面两个函数是不同的:

  1. fun returnDemo_1() {
  2. println(" START " + ::returnDemo_1.name)
  3. val intArray = intArrayOf(1, 2, 3, 4, 5)
  4. intArray.forEach {
  5. if (it == 3) return
  6. println(it)
  7. }
  8. println(" END " + ::returnDemo_2.name)
  9. }
  10. //1
  11. //2
  12. fun returnDemo_2() {
  13. println(" START " + ::returnDemo_2.name)
  14. val intArray = intArrayOf(1, 2, 3, 4, 5)
  15. intArray.forEach(fun(a: Int) {
  16. if (a == 3) return
  17. println(a)
  18. })
  19. println(" END " + ::returnDemo_2.name)
  20. }
  21. //1
  22. //2
  23. //4
  24. //5

returnDemo_1 在遇到 3 时会直接返回(有点类似循环体中的break行为)。最后输出

  1. 1
  2. 2

returnDemo_2 遇到 3 时会跳过它继续执行(有点类似循环体中的continue行为)。最后输出

  1. 1
  2. 2
  3. 4
  4. 5

returnDemo_2 中,我们用一个匿名函数替代 lambda 表达式。 匿名函数内部的 return 语句将从该匿名函数自身返回。

在Kotlin中,这是匿名函数和 lambda 表达式行为不一致的地方。当然,为了显式的指明 return 返回的地址,为此 kotlin 还提供了 @Label (标签) 来控制返回语句,且看下节分解。

3.5.7 标签(label)

在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@jarOfLove@ 都是有效的标签。我们可以用Label标签来控制 returnbreakcontinue的跳转(jump)行为。

Kotlin 的函数是可以被嵌套的。它有函数字面量、局部函数等。 有了标签限制的 return,我们就可以从外层函数返回了。例如,从 lambda 表达式中返回,returnDemo_2() 我们可以显示指定lambda 表达式中的return地址是其入口处。

代码示例:

  1. fun returnDemo_3() {
  2. println(" START " + ::returnDemo_3.name)
  3. val intArray = intArrayOf(1, 2, 3, 4, 5)
  4. intArray.forEach here@ {
  5. if (it == 3) return@here // 指令跳转到 lambda 表达式标签 here@ 处。继续下一个it=4的遍历循环
  6. println(it)
  7. }
  8. println(" END " + ::returnDemo_3.name)
  9. }
  10. //1
  11. //2
  12. //4
  13. //5

我们在 lambda 表达式开头处添加了标签here@ ,我们可以这么理解:该标签相当于是记录了Lambda表达式的指令执行入口地址, 然后在表达式内部我们使用return@here 来跳转至Lambda表达式该地址处。

另外,我们也可以使用隐式标签更方便。 该标签与接收该 lambda 的函数同名。

代码示例

  1. fun returnDemo_4() {
  2. println(" START " + ::returnDemo_4.name)
  3. val intArray = intArrayOf(1, 2, 3, 4, 5)
  4. intArray.forEach {
  5. if (it == 3) return@forEach // 从 lambda 表达式 @forEach 中返回。
  6. println(it)
  7. }
  8. println(" END " + ::returnDemo_4.name)
  9. }

接收该Lambda表达式的函数是forEach, 所以我们可以直接使用 return@forEach ,来跳转到此处执行下一轮循环。

通常当我们在循环体中使用break,是跳出最近外层的循环:

  1. fun breakDemo_1() {
  2. println("--------------- breakDemo_1 ---------------")
  3. for (outer in 1..5) {
  4. println("outer=" + outer)
  5. for (inner in 1..10) {
  6. println("inner=" + inner)
  7. if (inner % 2 == 0) {
  8. break
  9. }
  10. }
  11. }
  12. }

输出

  1. --------------- breakDemo_1 ---------------
  2. outer=1
  3. inner=1
  4. inner=2
  5. outer=2
  6. inner=1
  7. inner=2
  8. outer=3
  9. inner=1
  10. inner=2
  11. outer=4
  12. inner=1
  13. inner=2
  14. outer=5
  15. inner=1
  16. inner=2

当我们想直接跳转到外层for循环,这个时候我们就可以使用标签了。

代码示例

  1. fun breakDemo_2() {
  2. println("--------------- breakDemo_2 ---------------")
  3. outer@ for (outer in 1..5)
  4. for (inner in 1..10) {
  5. println("inner=" + inner)
  6. println("outer=" + outer)
  7. if (inner % 2 == 0) {
  8. break@outer
  9. }
  10. }
  11. }

输出

  1. --------------- breakDemo_2 ---------------
  2. inner=1
  3. outer=1
  4. inner=2
  5. outer=1

有时候,为了代码可读性,我们可以用标签来显式地指出循环体的跳转地址,比如说在breakDemo_1()中,我们可以用标签来指明内层循环的跳转地址:

  1. fun breakDemo_3() {
  2. println("--------------- breakDemo_3 ---------------")
  3. for (outer in 1..5)
  4. inner@ for (inner in 1..10) {
  5. println("inner=" + inner)
  6. println("outer=" + outer)
  7. if (inner % 2 == 0) {
  8. break@inner
  9. }
  10. }
  11. }

3.5.8 throw表达式

在 Kotlin 中 throw 是表达式,它的类型是特殊类型 Nothing。 该类型没有值。跟C、Java中的void 意思一样。

  1. >>> Nothing::class
  2. class java.lang.Void

我们在代码中,用 Nothing 来标记无返回的函数:

  1. >>> fun fail(msg:String):Nothing{ throw IllegalArgumentException(msg) }
  2. >>> fail("XXXX")
  3. java.lang.IllegalArgumentException: XXXX
  4. at Line57.fail(Unknown Source)

另外,如果把一个throw表达式的值赋值给一个变量,需要显式声明类型为Nothing , 代码示例如下

  1. >>> val ex = throw Exception("YYYYYYYY")
  2. error: 'Nothing' property type needs to be specified explicitly
  3. val ex = throw Exception("YYYYYYYY")
  4. ^
  5. >>> val ex:Nothing = throw Exception("YYYYYYYY")
  6. java.lang.Exception: YYYYYYYY

另外,因为ex变量是Nothing类型,没有任何值,所以无法当做参数传给函数:

  1. >>> println(ex)
  2. error: overload resolution ambiguity:
  3. @InlineOnly public inline fun println(message: Any?): Unit defined in kotlin.io
  4. @InlineOnly public inline fun println(message: Boolean): Unit defined in kotlin.io
  5. @InlineOnly public inline fun println(message: Byte): Unit defined in kotlin.io
  6. @InlineOnly public inline fun println(message: Char): Unit defined in kotlin.io
  7. @InlineOnly public inline fun println(message: CharArray): Unit defined in kotlin.io
  8. @InlineOnly public inline fun println(message: Double): Unit defined in kotlin.io
  9. @InlineOnly public inline fun println(message: Float): Unit defined in kotlin.io
  10. @InlineOnly public inline fun println(message: Int): Unit defined in kotlin.io
  11. @InlineOnly public inline fun println(message: Long): Unit defined in kotlin.io
  12. @InlineOnly public inline fun println(message: Short): Unit defined in kotlin.io
  13. println(ex)
  14. ^
  15. >>> ex
  16. exception: org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: Unregistered script: class Line62
  17. Cause: Unregistered script: class Line62
  18. File being compiled and position: (1,1) in /line64.kts
  19. PsiElement: ex
  20. The root cause was thrown at: ScriptContext.java:86
  21. ...