![Kotlin从入门到进阶实战](https://wfqqreader-1252317822.image.myqcloud.com/cover/633/26793633/b_26793633.jpg)
2.3 流程控制语句
流程控制语句是编程语言中的核心之一,可分为:
分支语句(if、when)
循环语句(for、while)
跳转语句(return、break、continue、throw)
2.3.1 if表达式
if…else语句是控制程序流程的最基本形式,其中else是可选的。在Kotlin中,if是一个表达式,即它会返回一个值(跟Scala一样)。代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P34_40827.jpg?sign=1739523997-gL0T7Yk74rhG2rB7S7HV99fpbYIxUaCG-0-d105ea8485d58066e4139cefb16d8978)
另外,if的分支可以是代码块,最后的表达式作为该块的值:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P34_40828.jpg?sign=1739523997-lhyPkDFfJI5jjdXsfsU7c0MbMWJ7sMuj-0-31227bf2149c58c7d1ee6c2a7ff2a25a)
if作为代码块时,最后一行为其返回值。另外,在Kotlin中没有类似true?1:0这样的三元表达式。对应的写法是使用if…else语句:
if(true) 1 else 0 //if…else实现三元表达式的逻辑
if…else语句规则:if后的括号不能省略,括号里表达式的值必须是布尔型。
代码反例:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P34_40829.jpg?sign=1739523997-YUkKvB2Is5qGtFBrkNK7jCSV3F4cBcYq-0-15419227fe9888e6ee72052e6e02cb51)
如果条件体内只有一条语句需要执行,那么if后面的大括号可以省略。良好的编程风格是建议加上大括号。
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P34_40830.jpg?sign=1739523997-et0cHoXaucE9ZW0Q3zKped66DuTCxDM6-0-72582efb674b13f280298f2835530e6d)
编程实例:用if…else语句判断某年份是否是闰年。
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P35_41073.jpg?sign=1739523997-A71GjPh16RCnGbDs37SQ3s1mgMPOR3hX-0-1a2932fda76709a882f49624d3c8a48c)
2.3.2 when表达式
when表达式类似于switch…case表达式。when会对所有的分支进行检查直到有一个条件被满足。但相比switch而言,when语句的功能要更加强大、灵活。
Kotlin的极简语法表达风格,使得我们对分支检查的代码写起来更加简单、直接:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P35_41074.jpg?sign=1739523997-hwaAWvd6RZGMAELC6dlJyXMAqCDisyc5-0-a548de24b35f9fd9516261212bc7b6f1)
输出如下:
1 ===> 这是一个0-9之间的数字 hello ===> 这个是字符串hello X ===> 这是一个Char类型数据 null ===> else类似于Java中的case-switch中的default
像if语句一样,when语句的每一个分支也可以是一个代码块,它的值是块中最后的表达式的值。如果其他分支都不满足条件会到else分支(类似default)。
如果我们有很多分支需要用相同的处理方式,则可以把多个分支条件放在一起,用逗号分隔:
0,1,2,3,4,5,6,7,8,9 -> println("${obj} ===> 这是一个0-9之间的数字")
可以用任意表达式(而不只是常量)作为分支条件:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P36_41309.jpg?sign=1739523997-dlKPVOgZ7TqOQO7xGNuEHnSncomNYY20-0-5c925ad60752be4416b8e6413ca992c9)
也可以检测一个值在in或者不在!in一个区间或者集合中:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P36_41310.jpg?sign=1739523997-lsjJ0O4U9au4uD5i1bBVBOKZ1xuxXci8-0-f712b736437badb31fcb7b5a51e08f4b)
编程实例:用when语句写一个阶乘函数。
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P36_41311.jpg?sign=1739523997-8p7BE8g905jSFQtmXdUuoFPmAGAzwutB-0-93190811ac80a551207d426ffc6e093a)
2.3.3 for循环
for循环可以对任何提供迭代器(iterator)的对象进行遍历,语法如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P36_41312.jpg?sign=1739523997-wUQLs6ht05q33LFO4Zq6Gyxu05xgoQbn-0-0341237f219b247d5957834bade1a18d)
如果想要通过索引遍历一个数组或者一个list,可以这么做:
for (i in array.indices) { //array.indices 存储了数组 array 的下标序列 print(array[i]) }
其中,array.indices持有数组的下标列表。我们也可以使用函数withIndex()来遍历下标与对应的元素:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P37_41315.jpg?sign=1739523997-gV448139pj2zpmYTlAxIj1AsgL6WsG7J-0-d451b47df65b724a4b2086b73ac60034)
另外,范围(Ranges)表达式也可用于循环中:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P37_41316.jpg?sign=1739523997-rlFeVdjtD4Ow727dfIG0gEAkpY2AWR2i-0-d176764bb262b349dac8a6a4de91f34b)
代码简写如下:
(1..10).forEach { print(it) }
其中的操作符形式的1..10等价于1.rangeTo(10)函数调用,由in和!in进行连接。
编程实例:编写一个Kotlin程序在屏幕上输出1!+2!+3!+…+10!的和。
我们使用上面的fact()函数,代码实现如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P37_41317.jpg?sign=1739523997-y36Q8plHbqV18CloB3YSqSnNc9Ef9znn-0-557191cd96e0b7555b35c68cce6bc5a9)
2.3.4 while循环
while和do…while循环语句的使用方式与C、Java语言基本一致。代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P37_41318.jpg?sign=1739523997-LiqMr2d4ZsOp6nn4B6HoT9PKMDtA16un-0-1f8385ac0c3a8d85908c69a384cf3d37)
2.3.5 break和continue
break和continue语句都是用来控制循环结构的,主要用来停止循环(中断跳转),但是二者又有区别,下面分别介绍。
break语句用于完全结束一个循环,直接跳出循环体,然后执行循环后面的语句。
1. 问题场景:打印数字1~10,只要遇到偶数就结束打印
下面我们使用for循环打印1~10之间的数字,遇到偶数就使用break语句结束循环。代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P38_41319.jpg?sign=1739523997-KBcWbe4CQt9ol4pJiVQedetuFyvf8liq-0-dddf1c1e670043bd83d70314f11e058e)
输出如下:
1 2
continue语句是只终止本轮循环,但是还会继续下一轮循环。可以简单理解为直接在当前语句处中断,跳转到循环入口,执行下一轮循环。而break语句则是完全终止循环,跳转到循环出口。
2. 问题场景:打印数字1~10中的奇数
下面我们使用for循环来打印1~10中的数字,遇到偶数就使用continue语句跳过本轮循环,实现只打印奇数的效果。代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P38_41320.jpg?sign=1739523997-lCaXgG0dR3MWbjepdbVZbV2Yf87yupqJ-0-ae4182ff23b40443a335e7fce77b7d78)
输出如下:
1 3 5 7 9
2.3.6 return返回
在Java和C语言中,return语句是很常见的了。虽然在Scala和Groovy类语言中,函数的返回值可以不需要显示用return语句来指定,但是我们仍然认为使用return语句的编码风格更容易阅读理解(尤其是在分支流代码块中)。
在Kotlin中,除了表达式的值,有返回值的函数都要求显式使用return语句返回其值。代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P39_41324.jpg?sign=1739523997-TuhuH7tAmI8oXCHyrZ6eQqQDWOBeUGWw-0-483bfcfce85590066ca35975b857cec4)
在Kotlin中可以直接使用“=”符号返回一个函数的值,这样的函数称为函数字面量。代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P39_41325.jpg?sign=1739523997-k0Uz4KJ4mjWAW1ZWiaha1uXioM2B6pGR-0-64d92efee9f5ba3d06c8418439e7f6c8)
代码说明如下:
第(1)处使用表达式声明sum()函数。
第(2)处ifelse表达式返回的是一个值,我们直接用表达式来定义一个max()函数。
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P39_41326.jpg?sign=1739523997-fOYquRgTWbDkdpayS8Qchhptxu47Fbgc-0-51ba559a12459093ede48b864124beb3)
第(3)处使用fun关键字声明了一个匿名函数,并且直接使用表达式来实现函数。需要注意的是,后面的函数体语句中有没有大括号{}代表的意义完全不同。例如下面的代码:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P39_41327.jpg?sign=1739523997-fncKG4gu3IHDQqLBQzv0y0PpFQUZCz9G-0-9688c82da42bdfe7d12f52e1ec3bd77d)
代码说明如下:
第(4)处带上大括号{}的表达式返回的是一个Lambda表达式。
第(5)处{a+b}的类型是从(kotlin.Int, kotlin.Int)到()->kotlin.Int的映射函数。
第(6)处调用了sumf()函数。
第(7)处中sumf(1,1)的返回值是一个函数()->kotlin.Int,而不是具体的数值2。如何实现真正的函数调用呢?请看第(8)处。
第(8)处使用invoke()函数实现真正调用函数。
在Kotlin中,()操作符对应的是类的重载函数,如invoke()。我们使用()运算符来调用函数。也就是说第(8)处与第(9)处是等价的写法。
我们也可以使用fun关键字加上函数名来声明函数,下面同样是展示带上大括号{}的例子,代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P40_1251.jpg?sign=1739523997-ht4KFq1e3Rvi2TkK4vRdUCzaEEzROxfL-0-605b29376864deb82b9761acbda127bb)
可以看出,sumf和maxf()的返回值都是函数类型:
() -> kotlin.Int
这点与Scala是不同的。在Scala中,带不带大括号{}的意义是一样的:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P40_41329.jpg?sign=1739523997-xJPYqJbFtr0tgYhhdG26FrEjU7bnJI4O-0-06c916693c3c274ceb34ce8d67f01e4f)
可以看出,maxf: (x: Int,y:Int)Int与maxv: (x: Int, y: Int)Int的签名是一样的。在这里,Kotlin与Scala在大括号的使用上是完全不同的。调用函数方式是直接调用invoke()函数sumf(1,1).invoke()。
Kotlin中return语句会从最近的函数或匿名函数中返回,但是在Lambda表达式中遇到return语句时,则直接返回最近的外层函数。例如下面两个函数是不同的:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P40_41330.jpg?sign=1739523997-r5ajLw7cxBky1CPTJAg7rN4HF6EsXJv5-0-263639ccab0e64fae29c5026ccde8358)
输出如下:
1 2
遇到3时会直接返回(类似于循环体中的break语句)。
而我们给forEach传入一个匿名函数fun(a:Int),这个匿名函数里的return语句不会跳出forEach循环,有点像continue语句的效果:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P41_41333.jpg?sign=1739523997-e39WGuQsfQICEJ2fHfLOZxSJ6MHfCPb9-0-1362c84787fec5afd621cd853599184a)
输出如下:
1 2 4 5
为了显式地指明return语句返回的地址,Kotlin还提供了@Label(标签)来控制返回语句,且看2.3.7节的讲解。
2.3.7 标签(label)
在Kotlin中任何表达式都可以用标签(label)来标记。标签的格式为标识符后跟@符号,如abc@、_isOK@都是有效的标签。我们可以用Label标签来控制return、break或continue语句的跳转(jump)行为。
代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P41_41336.jpg?sign=1739523997-lAkpSMIbJgXS3jWE9MXeJotVgMbxKBBU-0-07003eaf2e0120a5916af0cce458912e)
输出如下:
1 2 4 5
我们在Lambda表达式开头处添加了标签here@,可以这么理解:该标签相当于记录了Lambda表达式的指令执行入口地址,然后在表达式内部使用return@here跳转至Lambda表达式中的该地址处。这样代码更加易懂。
另外,也可以使用隐式标签,更加方便。该标签与接收该Lambda的函数同名。
代码示例如下:
val intArray = intArrayOf(1, 2, 3, 4, 5) intArray.forEach { if (it == 3) return@forEach //返回@forEach 处继续下一个循环 println(it) }
输出如下:
1 2 4 5
接收该Lambda表达式的函数是forEach,所以我们可以直接使用return@forEach跳转到此处执行下一轮循环。
2.3.8 throw表达式
在Kotlin中throw是表达式,它的类型是特殊类型Nothing。该类型没有值,与C、Java语言中的void意思一样。
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P42_41339.jpg?sign=1739523997-CctKYyHxhfVrtIsG0PBQ87na7mxE84Pm-0-e00e78d7f102437c7ffe944c0d9a1c75)
我们在代码中,用Nothing来标记无返回的函数:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P42_41340.jpg?sign=1739523997-qS4iApSkoJXBuy2vUGO1Gv1YrlOvHtuA-0-85c1e18a3485b22432dfa09d4ea6a976)
另外,如果把一个throw表达式的值赋给一个变量,需要显式声明类型为Nothing,代码示例如下:
![](https://epubservercos.yuewen.com/317ECE/15253386404112206/epubprivate/OEBPS/Images/Figure-P42_41341.jpg?sign=1739523997-YY95RVJz0Pcgl5PZodj1aRJHaYrCKcwm-0-5f5d5810ad1e8aa28caabc191764ea84)
另外,因为ex变量是Nothing类型,没有任何值,所以无法作为参数传给函数。