Kotlin 流程控制语句

2,252 阅读14分钟

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

  • 分支语句 (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, elseelse 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提供了 continuebreak 来控制循环结构。 除此之外,使用 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 处于多少层循环之内。