跳到主要内容

Kotlin 控制流

控制流是程序执行的核心机制,决定了代码的执行顺序和分支。Kotlin 提供了强大的条件表达式和循环结构,其中 when 表达式比传统的 switch 更加灵活,if 可以作为表达式直接返回值。本章将深入介绍这些控制流语句的用法和最佳实践。

表达式 vs 语句

在深入控制流之前,理解 Kotlin 中"表达式"和"语句"的区别非常重要:

表达式(Expression):有返回值的代码片段,可以赋值给变量或作为参数传递。

val x = 5          // 5 是表达式
val y = x + 3 // x + 3 是表达式
val max = if (a > b) a else b // if 表达式

语句(Statement):执行动作但不返回值的代码片段。

println("Hello")   // 语句
x = 10 // 赋值语句

Kotlin 的 ifwhen 既是语句也是表达式,这使代码更加简洁。

if 表达式

Kotlin 中的 if 不仅仅是语句,还可以作为表达式使用,这意味着它可以直接返回一个值。这与 Java 中需要三元运算符 condition ? then : else 不同,Kotlin 的 if 表达式本身就具备这种能力。

基本用法

fun main() {
val a = 10
val b = 20

// 传统用法:作为语句
if (a > b) {
println("a 大于 b")
} else if (a < b) {
println("a 小于 b")
} else {
println("a 等于 b")
}
}

if 作为表达式

if 作为表达式时,它会有返回值,此时 else 分支是必须的:

fun main() {
val a = 10
val b = 20

// if 表达式直接赋值给变量
val max = if (a > b) a else b
println("最大值: $max")

// 带有代码块的 if 表达式
// 注意:代码块中最后一个表达式的值就是整个块的返回值
val result = if (a > b) {
println("选择 a")
a // 这是返回值
} else {
println("选择 b")
b // 这是返回值
}
println("结果: $result")
}

理解代码块返回值:在 Kotlin 中,代码块中最后一行的表达式会自动成为该代码块的返回值。这个特性让代码更加简洁,不需要显式使用 return 关键字。

区间检查

结合 in 操作符,if 可以方便地检查值是否在某个范围内:

fun main() {
val x = 5

// 检查是否在区间内
if (x in 1..10) {
println("x 在 1 到 10 之间")
}

// 组合条件
if (x in 1..10 && x % 2 == 1) {
println("x 是 1-10 之间的奇数")
}

// 检查是否不在区间内
if (x !in 10..20) {
println("x 不在 10 到 20 之间")
}
}

when 表达式

when 是 Kotlin 中强大的条件表达式,可以替代 Java 中的 switch 语句,但功能更加强大和灵活。

基本语法

fun main() {
val x = 2

// when 作为语句(不返回值)
when (x) {
1 -> println("x 是 1")
2 -> println("x 是 2")
3, 4 -> println("x 是 3 或 4") // 多个值匹配同一分支
else -> println("x 是其他值")
}
}

when 作为表达式

when 作为表达式时,必须覆盖所有可能的情况,或者提供 else 分支:

fun main() {
val x = 3

// when 表达式返回值
val result = when (x) {
1 -> "one"
2 -> "two"
3 -> "three"
else -> "other"
}
println("结果: $result")
}

多种匹配方式

when 支持多种灵活的匹配方式:

fun main() {
val x = 15

when (x) {
// 精确匹配
1 -> println("值是 1")

// 多值匹配(逗号分隔)
2, 3, 4 -> println("值是 2、3 或 4")

// 区间匹配
in 5..10 -> println("值在 5 到 10 之间")
in 11..20 -> println("值在 11 到 20 之间")

// 不在区间内
!in 1..20 -> println("值不在 1 到 20 之间")

// 类型匹配
is Int -> println("这是 Int 类型,值为 $x")

else -> println("其他情况")
}
}

智能类型转换

当使用 is 进行类型检查后,编译器会自动进行智能类型转换:

fun main() {
val obj: Any = "Hello Kotlin"

when (obj) {
is String -> {
// obj 在这里自动转换为 String 类型
println("字符串长度: ${obj.length}")
println("内容: $obj")
}
is Int -> {
// obj 在这里自动转换为 Int 类型
println("整数值: $obj")
}
is List<*> -> {
println("列表大小: ${obj.size}")
}
else -> {
println("未知类型: ${obj::class.simpleName}")
}
}
}

无参数 when

when 可以不使用参数,此时分支条件就是布尔表达式,可以替代复杂的 if-else if 链:

fun main() {
val score = 85

// 无参数 when:替代 if-else if 链
val grade = when {
score >= 90 -> "A(优秀)"
score >= 80 -> "B(良好)"
score >= 70 -> "C(中等)"
score >= 60 -> "D(及格)"
else -> "F(不及格)"
}
println("成绩等级: $grade")

// 更复杂的条件
val temp = 25
val weather = when {
temp < 0 -> "冰冻"
temp < 10 -> "寒冷"
temp in 10..20 -> "凉爽"
temp in 21..30 -> "温暖"
else -> "炎热"
}
println("天气: $weather")
}

捕获 when 主体

可以将 when 的主体值捕获到变量中:

fun main() {
val result = when (val input = getInput()) {
is Success -> "成功: ${input.data}"
is Error -> "错误: ${input.message}"
is Loading -> "加载中..."
}
println(result)
}

// 假设的状态类
sealed class ApiResult
data class Success(val data: String) : ApiResult()
data class Error(val message: String) : ApiResult()
object Loading : ApiResult()

fun getInput(): ApiResult = Success("用户数据")

守卫条件(Guard Conditions)

Kotlin 2.1 预览特性

Guard 条件是 Kotlin 2.1 引入的预览特性,需要在编译选项中启用:

// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xwhen-guards")
}
}

或命令行:kotlinc -Xwhen-guards main.kt

Guard 条件允许在 when 分支中添加额外的条件判断,使复杂控制流更加清晰和简洁。语法为:在主条件后使用 if 添加守卫条件。

sealed interface Animal {
data class Cat(val isHungry: Boolean) : Animal {
fun feed() = println("喂猫")
}
data class Dog(val breed: String) : Animal {
fun play() = println("和狗玩耍")
}
}

fun main() {
val animal: Animal = Animal.Cat(isHungry = true)

when (animal) {
// 只有主条件:当 animal 是 Dog 时执行
is Animal.Dog -> animal.play()

// 主条件 + 守卫条件:当 animal 是 Cat 且 isHungry 为 true 时执行
is Animal.Cat if animal.isHungry -> {
println("猫饿了")
animal.feed()
}

// 主条件 + 守卫条件:当 animal 是 Cat 且 isHungry 为 false 时执行
is Animal.Cat if !animal.isHungry -> {
println("猫不饿")
}

// 默认分支
else -> println("未知动物")
}
}

Guard 条件的特点

  1. 短路求值:如果主条件不匹配,守卫条件不会被求值
  2. 类型智能转换:在守卫条件中可以访问主条件匹配后的属性
  3. 可组合性:单个 when 表达式中可以混合使用有守卫和无守卫的分支
fun processInput(input: Any) {
when (input) {
// 区间匹配 + 守卫条件
in 1..100 if input % 2 == 0 -> println("1-100 之间的偶数: $input")
in 1..100 if input % 2 == 1 -> println("1-100 之间的奇数: $input")

// 类型匹配 + 守卫条件
is String if input.length > 5 -> println("长字符串: $input")
is String -> println("短字符串: $input")

// 仅类型匹配
is Int -> println("整数: $input")

else -> println("其他: $input")
}
}

else if 支持

fun categorize(x: Int) {
when {
x > 0 if x % 2 == 0 -> println("正偶数")
x > 0 if x % 2 == 1 -> println("正奇数")
x < 0 -> println("负数")
else -> println("零")
}
}

实际应用场景

// 处理网络响应
sealed class Response {
data class Success(val data: String, val cached: Boolean) : Response()
data class Error(val code: Int, val message: String) : Response()
object Loading : Response()
}

fun handleResponse(response: Response) {
when (response) {
// 成功响应,但来自缓存
is Response.Success if response.cached -> {
println("显示缓存数据: ${response.data}")
}
// 成功响应,来自网络
is Response.Success -> {
println("显示最新数据: ${response.data}")
}
// 客户端错误
is Response.Error if response.code in 400..499 -> {
println("客户端错误: ${response.message}")
}
// 服务端错误
is Response.Error if response.code >= 500 -> {
println("服务器错误: ${response.message}")
}
// 其他错误
is Response.Error -> {
println("错误: ${response.code} - ${response.message}")
}
Response.Loading -> println("加载中...")
}
}

for 循环

for 循环用于遍历任何提供了迭代器的对象,包括区间、数组、集合等。

基本遍历

fun main() {
// 遍历区间
println("遍历 1 到 5:")
for (i in 1..5) {
print("$i ")
}
println()

// 遍历数组
val fruits = listOf("Apple", "Banana", "Orange")
println("\n遍历水果列表:")
for (fruit in fruits) {
println("- $fruit")
}

// 遍历字符
println("\n遍历字符串字符:")
for (c in "Kotlin") {
print("$c ")
}
}

区间变体

Kotlin 提供了多种创建和使用区间的方式:

fun main() {
// 闭区间(包含结束值)
print("1..5: ")
for (i in 1..5) print("$i ") // 1 2 3 4 5
println()

// 半开区间(不包含结束值)
print("1 until 5: ")
for (i in 1 until 5) print("$i ") // 1 2 3 4
println()

// 倒序区间
print("5 downTo 1: ")
for (i in 5 downTo 1) print("$i ") // 5 4 3 2 1
println()

// 带步长
print("1..10 step 2: ")
for (i in 1..10 step 2) print("$i ") // 1 3 5 7 9
println()

// 倒序带步长
print("10 downTo 1 step 3: ")
for (i in 10 downTo 1 step 3) print("$i ") // 10 7 4 1
println()

// 字符区间
print("'a'..'e': ")
for (c in 'a'..'e') print("$c ") // a b c d e
}

区间理解要点

  • 1..5 表示从 1 到 5 的闭区间,包含 5
  • 1 until 5 表示从 1 到 4,不包含 5
  • downTo 用于创建递减区间
  • step 用于指定步长

遍历索引

fun main() {
val fruits = listOf("Apple", "Banana", "Orange")

// 方式一:使用 indices 属性
println("使用 indices:")
for (i in fruits.indices) {
println("索引 $i: ${fruits[i]}")
}

// 方式二:使用 withIndex() 函数
println("\n使用 withIndex:")
for ((index, value) in fruits.withIndex()) {
println("$index: $value")
}
}

遍历 Map

fun main() {
val map = mapOf(
"name" to "张三",
"age" to "25",
"city" to "北京"
)

// 遍历键值对
for ((key, value) in map) {
println("$key -> $value")
}

// 只遍历键
println("\n所有键:")
for (key in map.keys) {
println(key)
}

// 只遍历值
println("\n所有值:")
for (value in map.values) {
println(value)
}
}

自定义迭代器

可以让自定义类支持 for 循环遍历:

// 实现 Iterable 接口
class Countdown(val start: Int) : Iterable<Int> {
override fun iterator(): Iterator<Int> {
return object : Iterator<Int> {
var current = start
override fun hasNext(): Boolean = current > 0
override fun next(): Int = current--
}
}
}

fun main() {
println("倒计时:")
for (i in Countdown(5)) {
print("$i... ")
}
println("发射!")
}

while 循环

while 循环在条件满足时持续执行代码块。

while 循环

while 先检查条件,再执行循环体:

fun main() {
var count = 0

// while 循环:先检查条件,后执行
while (count < 5) {
println("count = $count")
count++
}

// 实际应用:读取直到满足条件
var input: String
do {
print("请输入密码: ")
input = readln()
} while (input != "123456")
println("密码正确!")
}

do-while 循环

do-while 先执行循环体,再检查条件,保证至少执行一次:

fun main() {
var count = 0

// do-while:先执行,后检查条件
do {
println("count = $count")
count++
} while (count < 5)

// 实际应用:菜单选择
var choice: Int
do {
println("\n=== 菜单 ===")
println("1. 开始游戏")
println("2. 设置")
println("3. 退出")
print("请选择: ")
choice = readln().toIntOrNull() ?: 0

when (choice) {
1 -> println("开始游戏...")
2 -> println("打开设置...")
3 -> println("再见!")
else -> println("无效选择,请重试")
}
} while (choice != 3)
}

while 与 do-while 的区别

fun main() {
// 当条件一开始就不满足时
var x = 10

// while:条件不满足,不执行
while (x < 5) {
println("while: 这行不会打印")
}

// do-while:至少执行一次
do {
println("do-while: 这行会打印一次")
} while (x < 5)
}

循环控制

break 和 continue

fun main() {
// break:跳出整个循环
println("break 示例:")
for (i in 1..10) {
if (i == 5) break
print("$i ") // 1 2 3 4
}
println()

// continue:跳过本次迭代,继续下一次
println("continue 示例:")
for (i in 1..5) {
if (i == 3) continue
print("$i ") // 1 2 4 5
}
println()
}

标签(Label)

Kotlin 支持使用标签来控制嵌套循环的跳转:

fun main() {
// 使用标签跳出外层循环
println("带标签的 break:")
outer@ for (i in 1..3) {
for (j in 1..3) {
println("i = $i, j = $j")
if (i == 2 && j == 2) {
break@outer // 直接跳出外层循环
}
}
}
println("循环结束")

// 使用标签 continue
println("\n带标签的 continue:")
for (i in 1..3) {
inner@ for (j in 1..3) {
if (j == 2) continue@inner // 跳到内层循环的下一次迭代
println("i = $i, j = $j")
}
}
}

标签语法:在循环前使用 标签名@ 定义标签,然后在 breakcontinue 后使用 @标签名 引用它。

标签的实际应用

fun main() {
// 查找二维数组中的元素
val matrix = arrayOf(
intArrayOf(1, 2, 3),
intArrayOf(4, 5, 6),
intArrayOf(7, 8, 9)
)
val target = 5
var found = false

search@ for (i in matrix.indices) {
for (j in matrix[i].indices) {
if (matrix[i][j] == target) {
println("找到 $target 在位置 ($i, $j)")
found = true
break@search
}
}
}

if (!found) {
println("未找到 $target")
}
}

区间详解

区间(Range)是 Kotlin 中非常实用的特性,用于表示一个有序的值序列。

创建区间

fun main() {
// 整数区间
val intRange = 1..10 // 1 到 10(闭区间)
val intRange2 = 1 until 10 // 1 到 9(半开区间)
val intRange3 = 10 downTo 1 // 10 到 1(降序)
val intRange4 = 1..10 step 2 // 1, 3, 5, 7, 9

// 字符区间
val charRange = 'a'..'z'
println('c' in charRange) // true
println('A' in charRange) // false

// 检查成员
println(5 in 1..10) // true
println(0 in 1..10) // false
println(10 in 1..10) // true(闭区间包含边界)
println(10 in 1 until 10) // false(半开区间不包含边界)
}

区间操作

fun main() {
val range = 1..10

// 检查是否包含
println(5 in range) // true
println(0 in range) // false
println(15 in range) // false

// 区间迭代
for (i in range) {
print("$i ")
}
println()

// 区间反转
for (i in range.reversed()) {
print("$i ")
}
println()

// 区间属性
println("起始: ${range.first}") // 1
println("结束: ${range.last}") // 10
println("步长: ${range.step}") // 1
println("元素数量: ${range.count()}") // 10
}

区间的实际应用

fun main() {
// 验证输入范围
fun validateAge(age: Int): Boolean {
return age in 0..150
}

println(validateAge(25)) // true
println(validateAge(-1)) // false
println(validateAge(200)) // false

// 日期处理
fun isWeekend(day: Int): Boolean {
return day in 6..7 // 假设 6=周六, 7=周日
}

// 成绩等级判断
fun getGradeLevel(score: Int): String {
return when (score) {
in 90..100 -> "优秀"
in 80..89 -> "良好"
in 60..79 -> "及格"
in 0..59 -> "不及格"
else -> "无效分数"
}
}

println(getGradeLevel(85)) // 良好
println(getGradeLevel(92)) // 优秀
}

实战示例

示例一:九九乘法表

fun main() {
println("九九乘法表:")
for (i in 1..9) {
for (j in 1..i) {
print("$j × $i = ${i * j}\t")
}
println()
}
}

示例二:猜数字游戏

fun main() {
val target = (1..100).random()
var attempts = 0
var guessed = false

println("=== 猜数字游戏 ===")
println("我想了一个 1-100 之间的数字,你有 7 次机会!")

while (attempts < 7 && !guessed) {
attempts++
print("第 $attempts 次尝试,请输入: ")

val guess = readln().toIntOrNull()
if (guess == null) {
println("请输入有效数字!")
attempts--
continue
}

when {
guess < target -> println("太小了!")
guess > target -> println("太大了!")
else -> {
guessed = true
println("恭喜! 你用 $attempts 次猜对了!")
}
}
}

if (!guessed) {
println("游戏结束! 正确答案是 $target")
}
}

示例三:简单计算器

fun main() {
println("=== 简单计算器 ===")

while (true) {
print("请输入表达式(如 1 + 2),输入 q 退出: ")
val input = readln()

if (input == "q") {
println("再见!")
break
}

val parts = input.split(" ")
if (parts.size != 3) {
println("格式错误,请使用: 数字 运算符 数字")
continue
}

val num1 = parts[0].toDoubleOrNull()
val op = parts[1]
val num2 = parts[2].toDoubleOrNull()

if (num1 == null || num2 == null) {
println("请输入有效数字")
continue
}

val result = when (op) {
"+" -> num1 + num2
"-" -> num1 - num2
"*" -> num1 * num2
"/" -> {
if (num2 == 0.0) {
println("错误: 除数不能为零")
continue
}
num1 / num2
}
else -> {
println("不支持的运算符: $op")
continue
}
}

println("结果: $num1 $op $num2 = $result")
}
}

小结

本章我们学习了 Kotlin 的控制流:

  1. if 表达式:既可以作为语句,也可以作为表达式返回值
  2. when 表达式:强大的模式匹配工具,支持多种匹配方式
  3. for 循环:遍历区间、数组、集合等可迭代对象
  4. while 循环whiledo-while 两种形式
  5. 循环控制breakcontinue 和标签跳转
  6. 区间..untildownTostep 等操作符

Kotlin 的控制流语法简洁而强大,特别是 when 表达式和区间的使用,让代码更加清晰易读。

练习

  1. 使用 when 判断一个数是正数、负数还是零
  2. 使用 for 循环计算 1 到 100 的和
  3. 使用 when 实现成绩等级判断(A/B/C/D/F)
  4. 使用嵌套循环和标签实现寻找二维数组中的最大值及其位置
  5. 实现一个简单的登录验证程序(最多尝试 3 次)