流程控制语句是编程语言中的核心之二,可分为:
- 分支语句 (if, when)
- 循环语句(for、 while)
- 跳转语句(return、 break、 continue、 throw)
1 分支语句
Kotlin 提供了两种常见的分支控制结构: if 分支和 when 分支 。其中 if 分支使用布尔表达式或布尔值作为分支条件来进行分支控制;而 when 分支则更适用于复杂的条件。
一般来说,当条件较为简单且可能的情况很少时,使用 if 分支;当条件比较复杂且可能的情况很多时,可以考虑使用 when 分支。
1.1 if 分支
Kotlin 的 if分支既可作为语句使用,也可作为表达式使用。
与 Java 相似, Kotlin 的 if 语句有如下三种形式。
- 第一种形式:
if (expression) {
statements ...
}
- 第二种形式:
if (expression) {
statements ...
} else {
statements ...
}
- 第三种形式:
if (expression) {
statements ...
} else if (expression) {
statements ...
}
... // 可以有零个或多个 else if 语句
else { // 最后的 else 语句也可以省略
statements ...
}
在上面的条件语句中, if(expression)、 else if (expression)以及 else 后花括号括起来的多行代码被称为代码块,一个代码块通常被当成一个整体来执行(除非在运行过程中遇到 return、 break、 continue 等关键字 ),因此这个代码块也被称为条件执行体 。例如如下程序。
fun main(args: Array<String>) {
var age = 30
if (age > 20) {
// ֻ只有当 age > 20 时,下面用花括号括起来的代码块才会执行
// 用花括号括起来的语句是一个整体,要么一起执行,要么一起不执行
println("年龄己经大于20岁了")
println("20岁以上的人应该学会承担责任...")
}
// 如果if(logic expression)、 else if(logic expression)
// 和else后的代码块只有一行语句,则可以省略花括号,
// 因为单行语句本身就是一个整体,无须用花括号把它们定义成一个整体。
val a = 5
if (a > 4)
// 如果 a>4,则执行下面的执行体,只有一行代码作为代码块
println("a 大于 4")
else
// 否则,执行下面的执行体,只有一行代码作为代码块
println("a 不大于 4")
// 通常建议不要省略 if, else、 else if后执行体的花括号,
// 即使条件执行体只有一行代码, 也要保留花括号,这样会有更好的可读性,
// 而且保留花括号会减少发生错误的可能。 例如如下代码,则不能正常执行
var b = 5
if (b > 4)
// 如果 b>4,则执行下面的执行体,只有一行代码作为代码块
println("b 大于 4")
else
// 否则,执行下面的执行体,只有一行代码作为代码块
b--
// 对于下面代码而言 ,它已经不再是条件执行体的一部分,因此总会执行
println("b 不大于 4")
// 如果 if 后有多条语句作为条件执行体,若省略了这个条件执行体的花括号,则会引起编译错误。
var c = 5
if (c > 4)
// 如果 c > 4,则执行下面的执行体,将只有 c-- 一行代码为执行体
c--
// 下面是一行普通代码,不属于执行体
println("c大于 4")
// 此处的 else 将没有 if 语句,因此编译出错
// 因为 if 后的条件执行体省略了花括号,则系统只把 c-- 一行代码作为条件执行体,
// 当 c 语句执行结束后,if语句也就执行结束了。
// 后面的 println(”c 大于 4”)己经是一行普通代码了,不再属于条件执行体,
// 从而导致 else 语句没有 if 语句,从而引起编译错误。
else
// 否则,执行下面的执行体,只有一行代码作为代码块
println("c不大于 4")
}
1.2 if 表达式
Kotlin 的 if 分支还可作为表达式使用。也就是说,整个 if 表达式(包括 else 部分)最终 会返回一个值,因此 if 可以代替 Java 中的三目运算符。 例如如下代码。
fun main(args: Array<String>) {
var age = 20
// 将 if 表达式赋值给 str 变量
var str = if (age > 20) "age大于20"
else if (age < 20) "age小于20" else "age等于20"
println(str)
}
1.3 when 分支语句
when 分支取代了 Java 原有的 switch 语句,例如如下 Java程序。
public class SwitchTest {
public static void main(String[] args) {
char score = 'B';
switch(score) {
case 'A':
System.out.println("优秀");
break;
case 'B':
System.out.println("良好");
break;
case 'C':
System.out.println("中等");
break;
case 'D':
System.out.println("及格");
break;
default :
System.out.println("垃圾");
}
}
}
将上面程序改为使用 when 分支,将会变得更加简洁。如果 when 分支包含多条语句,则需要使用花括号将这些语句括成一个整体形成代码块。程序如下。
fun main(args: Array<String>) {
var score = 'B'
when (score) {
'A' -> {
println("优秀")
println("望百尺竿头更进一步")
}
'B' -> {
println("良好")
println("不拼一把,你不知道自己的能力")
}
'C' -> "中等"
'D' -> "及格"
else -> {
println("垃圾")
println("回家洗洗睡吧")
}
}
}
when 分支的改变有以下几点。
- 不再需要使用 case 关键字。
- case 值后的冒号改为使用箭头(->)。
- default改为更有意义、更明确的 else。
如果 when 分支只是 switch 分支的简化,那也不过如此。事实上, when 分支比 switch 分 支更强大,下面是 when 分支的 3 个小改进。
- when 分支可以匹配多个值。
- when 分支后的值不要求是常量,可以是任意表达式。
- when 分支对条件表达式的类型没有任何要求。
when 分支后的值不再要求是常量或字面值,可以是任意表达式
fun main(args: Array<String>) {
var score = 'B'
var str = "EFGH"
when (score) {
str[0] - 4, str[1] - 4 -> {
println("优秀")
println("望百尺竿头更进一步")
}
str[2] - 4, str[3] - 4 -> println("中等")
else -> {
println("垃圾")
println("回家洗洗睡吧")
}
}
}
此外, when 分支不再对条件表达式的类型有任何要求, when 分支的条件表达式可以是任 意类型。
import java.util.Date
fun main(args: Array<String>){
var date = Date()
// when 分支的条件表达式是 Date 类型
when (date){
Date() -> {
println("优秀")
println("望百尺竿头更进一步")
}
else -> {
println("垃圾")
println("回家洗洗睡吧")
}
}
}
此时 when 分支的条件表达式是 Date 对象,但这不影响 when 分支的执行,只要 when 的条件表达式与某个分支的值通过“==”比较返回 true,程序就可以进入执行该分支的代码。
1.4 when 表达式
与 if 分支相同, when 分支也可作为表达式。
如果 when 分支被当作表达式,那么符合条件的分支的代码块的值就是整个表达式的值。 与 if 分支相同的是,如果分支的执行体是一个代码块,那么该代码块的值就是块中最后的表 达式的值。
当 when 语句作为表达式使用时, when 表达式也需要有一个返回值,因此 when 表达式通 常必须有 else 分支,除非编译器能够检测出所有的可能情况都己经被覆盖了。
fun main(args: Array<String>){
var score = 'B'
val str = when (score){
'A' -> {
println("优秀")
println ("望百尺竿头更进一步")
}
'B' -> {
println("良好")
println("不拼一把,你不知道自己的能力")
}
'C' -> "中等"
'D' -> "及格"
else -> {
println("垃圾")
println("回家洗洗睡吧")
}
}
println(str)
}
1.5 when 分支处理范围
fun main(args: Array<String>) {
val age = java.util.Random().nextInt(100)
println(age)
// 使用 when 表达式对 str 赋值
var str = when (age) {
in 10..25 -> "地振高冈,一派西山千古秀"
in 26..50 -> "门朝大海,三河河水万年流"
in 51..80 -> "天地会"
else -> "鹿鼎记"
}
println(str)
}
上面程序使用 when 表达式对 age 变量进行判断,该 when 表达式不再要求 age 等于某个 具体的值,而是只要age处于特定范围中,即可进入相应的分支。
1.6 when 分支处理类型
fun main(args: Array<String>) {
var inputPrice = 26
println(realPrice(inputPrice))
}
// 程序对 inputPrice 的类型进行判断
fun realPrice(inputPrice: Any) = when (inputPrice) {
// 如果 inputPrice 的类型为 String, 程序返回该字符串转换的 Double 值
is String -> inputPrice.toDouble()
// 如果 inputPrice 的类型为 Int,程序返回该 Int 值转换的 Double 值
is Int -> inputPrice.toDouble()
is Double -> inputPrice
else -> 0.0
}
上面程序使用 when表达式对 inputPrice 的类型进行判断。程序中 inputPrice 参数可能是任 意类型,因此程序判断如果 inputPrice 是 String 类型,则调用 String 对象的 toDouble() 将其转换为 Double 后返回;如果 inputPrice 是 Int 类型,则调用 Int 的 toDouble() 将其转换为 Double 后返回;如果 inputPrice是 Double类型,则直接返回 inputPrice。
1.7 when 分支处理类型
when 分支还可以用来取代 if...else if 链,此时不需要为 when 分支提供任何条件表达式, 每个分支条件都是一个布尔表达式,当指定分支的布尔表达式为 true 时执行该分支。例如如 下代码。
fun main(args: Array<String>) {
// 读取一行输入
val ln = readLine()
// ln 是 String? 类型,所以需要先判断 ln 不为 null
if (ln != null) {
// when 分支不需要任何条件表达式
when {
// 每个分支条件都是布尔表达式
ln.matches(Regex("\\d+")) -> println("您输入的全是数字")
ln.matches(Regex("[a-zA-Z]+")) -> println("您输入的全是字母")
ln.matches(Regex("[a-zA-Z0-9]+")) -> println("您输入的是字母和数字")
else -> println("您输入的包含特殊字符 ")
}
}
}
输出:
123456
您输入的全是数字
2 循环结构
循环语句可以在满足循环条件的情况下,反复执行某一段代码,这段被重复执行的代码被 称为循环体。当反复执行这个循环体时,需要在合适的时候把循环条件改为假,从而结束循环;否则循环将一直执行下去,形成死循环。
2.1 for-in 循环
for-in 循环专门用于遍历范围、序列和集合等包含的元素。 for-in 循环的语法格式如下:
for (常量名 in 字符串|范围|集合){
statements
}
如果想要通过索引遍历一个数组或者一个 list,可以这么做:
for (i in array.indices) { // array. indices 存储了数组 array 的下标序列
println (array[i))
}
范围(Ranges)表达式也可用于循环中:
for (i in 1..10) { //等同于1 <= i && i<= 10
println (array[i))
}
代码简写如下:
(1 .. 10).forEach { println (it) }
2.2 while 循环
下面程序示范了一个简单的 while循环。
fun main(args: Array<String>) {
// 循环的初始化条件
var count = 0
// 当 count 小子 10 时,执行循环体
while (count < 10) {
println("count:${count}")
// 法代语句
count++
}
println("循环结束!")
// 下面是一个死循环
var count2 = 0
while (count2 < 10) {
print("不停执行的死循环 ${count2}")
count2--
}
println("永远无法跳出的循环体")
}
使用 while 循环时,一定要保证循环条件有变成假的时候:否则这个循环将成为一个死循 环,永远无法结束这个循环。
2.3 do while 循环
do while 循环与 while 循环的区别在于: while 循环是先判断循环条件,如果条件为真则执 行循环体;而 do while 循环则先执行循环体,然后才判断循环条件,如果循环条件为真,则执 行下一次循环,否则中止循环。
fun main(args: Array<String>) {
// 定义变量 count
var count = 20
// 执行 do while 循环
do {
println(count)
// 循环法代语句
count++
// 循环条件紧跟 while 关键字
} while (count < 10)
println("循环结束 !")
}
输出:
20
循环结束 !
从上面程序来看,虽然开始 count 的值就是 20, count < 10 表达式返回 false,但 do while 循环还是会执行一次循环体。
2.3 嵌套循环
如果把一个循环放在另一个循环体内,那么就可以形成嵌套循环 。嵌套循环既可以是 for-in 循环嵌套 while 循环,也可以是 while 循环嵌套 do while 循环 ... 即各种类型的循环都可以作 为外层循环,各种类型的循环也都可以作为内层循环。
fun main(args: Array<String>) {
// 外层循环
for (i in 0 until 3) {
var j = 0
// 内层循环
while (j < 3) {
println("i 的值为: ${i} , j 的值为: ${j++}")
}
}
}
输出:
i 的值为: 0 , j 的值为: 0
i 的值为: 0 , j 的值为: 1
i 的值为: 0 , j 的值为: 2
i 的值为: 1 , j 的值为: 0
i 的值为: 1 , j 的值为: 1
i 的值为: 1 , j 的值为: 2
i 的值为: 2 , j 的值为: 0
i 的值为: 2 , j 的值为: 1
i 的值为: 2 , j 的值为: 2
2.4 控制循环结构
Kotlin提供了 continue 和 break 来控制循环结构。 除此之外,使用 return 可以结束整个方法,当然也就结束了一次循环。
2.4.1 使用 break 结束循环
fun main(args: Array<String>) {
// 一个简单的for循环
for (i in 0..10) {
println("i 的值是: ${i}")
if (i == 2) {
// ִ执行该语句时将结束循环
break
}
}
}
输出:
i 的值是: 0
i 的值是: 1
i 的值是: 2
运行上面程序,将看到 i 循环到 2 时即结束,当 i 等于 2 时,在循环体内遇到 break 语句,程序跳出该循环。
使用 break 语句不仅可以结束其所在的循环,还可以直接结束其外层循环。 此时需要在 break 后紧跟一个标签,这个标签用于标识一个外层循环。
Kotlin 中的标签就是一个紧跟着@的标识符,标识符的名字可以随便取。 Kotlin 中的标签只有放在循环语句或 switch 语句之前才起作用。例如下面代码。
fun main(args: Array<String>) {
// 外层循环, outer 作为标识符
outer@ for (i in 0 until 5) {
// 内层循环
for (j in 0 until 3) {
println("i 的值为:${i}, j 的值为:${j}")
if (j == 1) {
// 跳出 outer 标签所标识的循环
break@outer
}
}
}
}
输出:
i 的值为:0, j 的值为:0
i 的值为:0, j 的值为:1
程序从外层循环进入内层循环后,当 j 等于 1 时, 程序遇到一条 break@outer语句,这条语句将会导致结束 outer@标签指定的循环,不是结束 break 所在的循环,而是结束 outer@ 标签所标识的外层循环。所以会看到上面的运行结果。
值得指出的是, break 后的标签必须是一个有效的标签,即这个标签必须在 break 语句所 在的循环之前定义,或者在其所在循环的外层循环之前定义。当然,如果把这个标签放在 break 语句所在的循环之前定义,也就失去了标签的意义,因为 break 默认就是结束其所在的循环。
2.4.2 使用 continue 忽略本次循环的剩下语句
continue 的功能和 break有点类似,区别是 continue 只是忽略本次循环的剩下语句,接着开始下一次循环,并不会中止循环;而 break 则是完全中止循环本身。如下程序示范了 continue 的用法。
fun main(args: Array<String>) {
// 一个简单的 for 循环
for (i in 0 until 3) {
println("i 的值是 ${i}")
if (i == 1) {
// 忽略本次循环的剩下语句
continue
}
println("continue 后的输出语句")
}
}
输出:
i 的值是 0
continue 后的输出语句
i 的值是 1
i 的值是 2
continue 后的输出语句
从上面的运行结果来看,当 i 等于 1 时,程序没有输出“continue后的输出语句”字符串, 因为程序执行到 continue 时,忽略了当次循环中 continue 语句后的代码。从这个意义上看,如 果把一条 continue 语句放在当次循环的最后一行,那么这条 continue 语句是没有任何意义的,因为它仅仅忽略了一片空白,没有忽略任何程序语句。
与 break 类似的是, continue 后也可以紧跟一个标签,用于直接跳过标签所标识循环的当次循环的剩下语句,重新开始下一次循环。例如下面代码。
fun main(args: Array<String>) {
// 外层循环
outer@ for (i in 0 until 3) {
// 内层循环
for (j in 0 until 3) {
println("i 的值为:${i},j 的值为:${j}")
if (j == 1) {
// 忽略 outer 标签所指定的循环中当次循环剩下的语句
continue@outer
}
}
}
}
输出:
i 的值为:0,j 的值为:0
i 的值为:0,j 的值为:1
i 的值为:1,j 的值为:0
i 的值为:1,j 的值为:1
i 的值为:2,j 的值为:0
i 的值为:2,j 的值为:1
运行上面程序可以看到 ,循环变量 j 的值将无法超过 1,因为每当 j等于 1 时, continue@outer 语句就结束了外层循环的当次循环,直接开始下一次循环,内层循环没有机会执行完成。
与 break 类似的是, continue 后的标签也必须是一个有效的标签,即这个标签通常应该放 在 continue 所在循环的外层循环之前定义。
2.4.3 使用 return 结束方法
return 用于从最直接包围它的方法、函数或者匿名函数返回。当函数或方法执行到一条 return 语句时( return 关键字后还可以跟变量、常量和表达式),这个函数或方法将被结束。
Kotlin 程序中的大部分循环都被放在函数或方法中执行, 一旦在循环体内执行到一条 return 语句时,return 语句就会结束该函数或方法,循环自然也随之结束。例如下面程序。
fun main(args: Array<String>) {
test()
}
fun test() {
// 一个简单的 for 循环
for (i in 0 until 10) {
println("i 的值是: ${i}")
if (i == 1) {
return
}
println("return 后的输出语句")
}
}
输出:
i 的值是: 0
return 后的输出语句
i 的值是: 1
运行上面程序,循环只能执行到 i 等于 1 时,当 i 等于 1 时程序将完全结束(当 test()函数结束时,也就是 Kotlin 程序结束时)。从这个运行结果来看,虽然 return 并不是专门用于控制循环结构的关键字,但通过 return 语句确实可以结束一个循环。与 continue 和 break不同的是,return 直接结束整个函数或方法,而不管 return 处于多少层循环之内。