流程控制语句是编程语言中的核心之一。可分为:
分支语句(
if
、when
) 循环语句(for
、while
)和 跳转语句 (return
、break
、continue
、throw
)等。
if表达式
if-else语句是控制程序流程的最基本的形式,其中else是可选的。
在 Kotlin 中,if 是一个表达式,即它会返回一个值(跟Scala一样)。
代码示例:
package com.easy.kotlin fun main(args: Array<String>) { println(max(1, 2)) } fun max(a: Int, b: Int): Int { // 作为表达式 val max = if (a > b) a else b return max // return if (a > b) a else b } fun max1(a: Int, b: Int): Int { // 传统用法 var max1 = a if (a < b) max1 = b return max1 } fun max2(a: Int, b: Int): Int { // With else var max2: Int if (a > b) { max2 = a } else { max2 = b } return max2 }
另外,if 的分支可以是代码块,最后的表达式作为该块的值:
fun max3(a: Int, b: Int): Int { val max = if (a > b) { print("Max is a") a } else { print("Max is b") b } return max }
if作为代码块时,最后一行为其返回值。
另外,在Kotlin中没有类似true? 1: 0
这样的三元表达式。对应的写法是使用if else
语句:
if(true) 1 else 0
如果 if 表达式只有一个分支, 或者分支的结果是 Unit , 它的值就是 Unit 。
示例代码
>>> val x = if(1==1) true >>> x kotlin.Unit >>> val y = if(1==1) true else false >>> y true
if-else语句规则:
- if后的括号不能省略,括号里表达式的值须是布尔型
代码反例:
>>> if("a") 1 error: type mismatch: inferred type is String but Boolean was expected if("a") 1 ^ >>> if(1) println(1) error: the integer literal does not conform to the expected type Boolean if(1) ^
- 如果条件体内只有一条语句需要执行,那么if后面的大括号可以省略。良好的编程风格建议加上大括号。
>>> if(true) println(1) else println(0) 1 >>> if(true) { println(1)} else{ println(0)} 1
- 对于给定的if,else语句是可选的,else if 语句也是可选的。
- else和else if同时出现时,else必须出现在else if 之后。
- 如果有多条else if语句同时出现,那么如果有一条else if语句的表达式测试成功,那么会忽略掉其他所有else if和else分支。
- 如果出现多个if,只有一个else的情形,else子句归属于最内层的if语句。
以上规则跟Java、C语言基本相同。
when表达式
when表达式类似于 switch-case 表达式。when会对所有的分支进行检查直到有一个条件满足。但相比switch而言,when语句要更加的强大,灵活。
Kotlin的极简语法表达风格,使得我们对分支检查的代码写起来更加简单直接:
fun cases(obj: Any) { when (obj) { 1 -> print("第一项") "hello" -> print("这个是字符串hello") is Long -> print("这是一个Long类型数据") !is String -> print("这不是String类型的数据") else -> print("else类似于Java中的default") } }
像 if 一样,when 的每一个分支也可以是一个代码块,它的值是块中最后的表达式的值。
如果其他分支都不满足条件会到 else 分支(类似default)。
如果我们有很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:
fun switch(x: Any) { when (x) { -1, 0 -> print("x == -1 or x == 0") 1 -> print("x == 1") 2 -> print("x == 2") else -> { // 注意这个块 print("x is neither 1 nor 2") } } }
我们可以用任意表达式(而不只是常量)作为分支条件
fun switch(x: Int) { val s = "123" when (x) { -1, 0 -> print("x == -1 or x == 0") 1 -> print("x == 1") 2 -> print("x == 2") 8 -> print("x is 8") parseInt(s) -> println("x is 123") else -> { // 注意这个块 print("x is neither 1 nor 2") } } }
我们也可以检测一个值在 in 或者不在 !in 一个区间或者集合中:
val x = 1 val validNumbers = arrayOf(1, 2, 3) when (x) { in 1..10 -> print("x is in the range") in validNumbers -> print("x is valid") !in 10..20 -> print("x is outside the range") else -> print("none of the above") }
for循环
Kotlin的for循环跟现代的程序设计语言基本相同。
for 循环可以对任何提供迭代器(iterator)的对象进行遍历,语法如下:
for (item in collection) { print(item) }
循环体可以是一个代码块。
for (i in intArray) { ... }
代码示例
/** * For loop iterates through anything that provides an iterator. * See http://kotlinlang.org/docs/reference/control-flow.html#for-loops */ fun main(args: Array<String>) { for (arg in args) println(arg) // or println() for (i in args.indices) println(args[i]) }
如果你想要通过索引遍历一个数组或者一个 list,你可以这么做:
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使用方式跟C、Java语言基本一致。
代码示例
package com.easy.kotlin fun main(args: Array<String>) { var x = 10 while (x > 0) { x-- println(x) } var y = 10 do { y = y + 1 println(y) } while (y < 20) // y的作用域包含此处 }
break 和 continue
break
和continue
都是用来控制循环结构的,主要是用来停止循环(中断跳转)。
1.break
我们在写代码的时候,经常会遇到在某种条件出现的时候,就直接提前终止循环。而不是等到循环条件为false
时才终止。这个时候,我们就可以使用break
结束循环。break
用于完全结束一个循环,直接跳出循环体,然后执行循环后面的语句。
问题场景:打印数字1~10,只要遇到偶数,就结束打印。
代码示例:
fun breakDemo_1() { for (i in 1..10) { println(i) if (i % 2 == 0) { break } } // break to here }
测试代码:
@Test fun testBreakDemo_1(){ breakDemo_1() }
输出:
1 2
2.continue
continue
是只终止本轮循环,但是还会继续下一轮循环。可以简单理解为,直接在当前语句处中断,跳转到循环入口,执行下一轮循环。而break
则是完全终止循环,跳转到循环出口。
问题场景:打印数字0~10,但是不打印偶数。
代码示例:
fun continueDemo() { for (i in 1..10) { if (i % 2 == 0) { continue } println(i) } }
测试代码
@Test fun testContinueDemo() { continueDemo() }
输出
1 3 5 7 9
return返回
在Java、C语言中,return语句使我们再常见不过的了。虽然在Scala,Groovy这样的语言中,函数的返回值可以不需要显示用return来指定,但是我们仍然认为,使用return的编码风格更加容易阅读理解。
在Kotlin中,除了表达式的值,有返回值的函数都要求显式使用return
来返回其值。
代码示例
fun sum(a: Int,b: Int): Int{ return a+b } fun max(a: Int, b: Int): Int { if (a > b) return a else return b}
我们在Kotlin中,可以直接使用=
符号来直接返回一个函数的值。
代码示例
>>> fun sum(a: Int,b: Int) = a + b >>> fun max(a: Int, b: Int) = if (a > b) a else b >>> sum(1,10) 11 >>> max(1,2) 2 >>> val sum=fun(a:Int, b:Int) = a+b >>> sum (kotlin.Int, kotlin.Int) -> kotlin.Int >>> sum(1,1) 2 >>> val sumf = fun(a:Int, b:Int) = {a+b} >>> sumf (kotlin.Int, kotlin.Int) -> () -> kotlin.Int >>> sumf(1,1) () -> kotlin.Int >>> sumf(1,1).invoke() 2
上述代码示例中,我们可以看到,后面的函数体语句有没有大括号 {}
意思完全不同。加了大括号,意义就完全不一样了。我们再通过下面的代码示例清晰的看出:
>>> fun sumf(a:Int,b:Int) = {a+b} >>> sumf(1,1) () -> kotlin.Int >>> sumf(1,1).invoke error: function invocation 'invoke()' expected sumf(1,1).invoke ^ >>> sumf(1,1).invoke() 2 >>> fun maxf(a:Int, b:Int) = {if(a>b) a else b} >>> maxf(1,2) () -> kotlin.Int >>> maxf(1,2).invoke() 2
可以看出,sumf
,maxf
的返回值是函数类型:
() -> kotlin.Int () -> kotlin.Int
这点跟Scala是不同的。在Scala中,带不带大括号{}
,意思一样:
scala> def maxf(x:Int, y:Int) = { if(x>y) x else y } maxf: (x: Int, y: Int)Int scala> def maxv(x:Int, y:Int) = if(x>y) x else y maxv: (x: Int, y: Int)Int scala> maxf(1,2) res4: Int = 2 scala> maxv(1,2) res6: Int = 2
我们可以看出maxf: (x: Int, y: Int)Int
跟maxv: (x: Int, y: Int)Int
签名是一样的。在这里,Kotlin跟Scala在大括号的使用上,是完全不同的。
然后,调用方式是直接调用invoke()
函数。通过REPL的编译错误提示信息,我们也可以看出,在Kotlin中,调用无参函数也是要加上括号()
的。
kotlin 中 return
语句会从最近的函数或匿名函数中返回,但是在Lambda表达式中遇到return,则直接返回最近的外层函数。例如下面两个函数是不同的:
fun returnDemo_1() { println(" START " + ::returnDemo_1.name) val intArray = intArrayOf(1, 2, 3, 4, 5) intArray.forEach { if (it == 3) return println(it) } println(" END " + ::returnDemo_2.name) } //1 //2 fun returnDemo_2() { println(" START " + ::returnDemo_2.name) val intArray = intArrayOf(1, 2, 3, 4, 5) intArray.forEach(fun(a: Int) { if (a == 3) return println(a) }) println(" END " + ::returnDemo_2.name) } //1 //2 //4 //5
returnDemo_1
在遇到 3 时会直接返回(有点类似循环体中的break
行为)。最后输出
1 2
returnDemo_2
遇到 3 时会跳过它继续执行(有点类似循环体中的continue
行为)。最后输出
1 2 4 5
在returnDemo_2
中,我们用一个匿名函数替代 lambda 表达式。 匿名函数内部的 return 语句将从该匿名函数自身返回。
在Kotlin中,这是匿名函数和 lambda 表达式行为不一致的地方。当然,为了显式的指明 return
返回的地址,为此 kotlin 还提供了 @Label
(标签) 来控制返回语句,且看下节分解。
标签(label)
在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @
符号,例如:abc@
、jarOfLove@
都是有效的标签。我们可以用Label标签来控制 return
、break
或 continue
的跳转(jump)行为。
Kotlin 的函数是可以被嵌套的。它有函数字面量、局部函数等。 有了标签限制的 return,我们就可以从外层函数返回了。例如,从 lambda 表达式中返回,returnDemo_2()
我们可以显示指定lambda 表达式中的return地址是其入口处。
代码示例:
fun returnDemo_3() { println(" START " + ::returnDemo_3.name) val intArray = intArrayOf(1, 2, 3, 4, 5) intArray.forEach here@ { if (it == 3) return@here // 指令跳转到 lambda 表达式标签 here@ 处。继续下一个it=4的遍历循环 println(it) } println(" END " + ::returnDemo_3.name) } //1 //2 //4 //5
我们在 lambda 表达式开头处添加了标签here@
,我们可以这么理解:该标签相当于是记录了Lambda表达式的指令执行入口地址, 然后在表达式内部我们使用return@here
来跳转至Lambda表达式该地址处。
另外,我们也可以使用隐式标签更方便。 该标签与接收该 lambda 的函数同名。
代码示例
fun returnDemo_4() { println(" START " + ::returnDemo_4.name) val intArray = intArrayOf(1, 2, 3, 4, 5) intArray.forEach { if (it == 3) return@forEach // 从 lambda 表达式 @forEach 中返回。 println(it) } println(" END " + ::returnDemo_4.name) }
接收该Lambda表达式的函数是forEach, 所以我们可以直接使用 return@forEach
,来跳转到此处执行下一轮循环。
通常当我们在循环体中使用break,是跳出最近外层的循环:
fun breakDemo_1() { println("--------------- breakDemo_1 ---------------") for (outer in 1..5) { println("outer=" + outer) for (inner in 1..10) { println("inner=" + inner) if (inner % 2 == 0) { break } } } }
输出
--------------- breakDemo_1 --------------- outer=1 inner=1 inner=2 outer=2 inner=1 inner=2 outer=3 inner=1 inner=2 outer=4 inner=1 inner=2 outer=5 inner=1 inner=2
当我们想直接跳转到外层for循环,这个时候我们就可以使用标签了。
代码示例
fun breakDemo_2() { println("--------------- breakDemo_2 ---------------") outer@ for (outer in 1..5) for (inner in 1..10) { println("inner=" + inner) println("outer=" + outer) if (inner % 2 == 0) { break@outer } } }
输出
--------------- breakDemo_2 --------------- inner=1 outer=1 inner=2 outer=1
有时候,为了代码可读性,我们可以用标签来显式地指出循环体的跳转地址,比如说在breakDemo_1()
中,我们可以用标签来指明内层循环的跳转地址:
fun breakDemo_3() { println("--------------- breakDemo_3 ---------------") for (outer in 1..5) inner@ for (inner in 1..10) { println("inner=" + inner) println("outer=" + outer) if (inner % 2 == 0) { break@inner } } }
throw表达式
在 Kotlin 中 throw 是表达式,它的类型是特殊类型 Nothing。 该类型没有值。跟C、Java中的void
意思一样。
>>> Nothing::class class java.lang.Void
我们在代码中,用 Nothing 来标记无返回的函数:
>>> fun fail(msg:String):Nothing{ throw IllegalArgumentException(msg) } >>> fail("XXXX") java.lang.IllegalArgumentException: XXXX at Line57.fail(Unknown Source)
另外,如果把一个throw表达式的值赋值给一个变量,需要显式声明类型为Nothing
, 代码示例如下
>>> val ex = throw Exception("YYYYYYYY") error: 'Nothing' property type needs to be specified explicitly val ex = throw Exception("YYYYYYYY") ^ >>> val ex:Nothing = throw Exception("YYYYYYYY") java.lang.Exception: YYYYYYYY
另外,因为ex变量是Nothing类型,没有任何值,所以无法当做参数传给函数:
>>> println(ex) error: overload resolution ambiguity: @InlineOnly public inline fun println(message: Any?): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Boolean): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Byte): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Char): Unit defined in kotlin.io @InlineOnly public inline fun println(message: CharArray): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Double): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Float): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Int): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Long): Unit defined in kotlin.io @InlineOnly public inline fun println(message: Short): Unit defined in kotlin.io println(ex) ^ >>> ex exception: org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: Unregistered script: class Line62 Cause: Unregistered script: class Line62 File being compiled and position: (1,1) in /line64.kts PsiElement: ex The root cause was thrown at: ScriptContext.java:86 ...