4. Kotlin基础语法(二):条件表达式(if/when)、循环(for/while)、区间与步长

好,咱们接着聊。上一章我们把变量、函数、空安全这些基础打好了,这一章要讲的是控制流——说白了,就是让代码“动起来”的东西。

条件判断和循环,是任何编程语言都绕不开的核心。但Kotlin在这块做了不少“升级”,让代码写起来更简洁、更安全。我个人觉得,这是从Java转Kotlin后,最先能感受到“爽”的地方之一。

4.1 条件表达式:if 的“变形记”

在Java里,if就是个语句,执行完拉倒。但在Kotlin里,if是个表达式——它能有返回值。这个区别,你想想看,直接改变了我们写代码的方式。

4.1.1 if 作为表达式

先看个最直观的例子:

// Java 写法
int max;
if (a > b) {
    max = a;
} else {
    max = b;
}

// Kotlin 写法
val max = if (a > b) a else b

看到了吗?一行搞定。而且因为if有返回值,我们可以直接把它赋值给一个变量。这里有个关键点:如果if的分支里有多行代码,最后一行的值就是返回值

核心区别:Java的if是语句,Kotlin的if是表达式。表达式可以有返回值,语句不行。

我在项目中遇到过不少刚转Kotlin的同事,还是习惯先声明一个变量,再用if去赋值。其实完全没必要,直接val + if表达式,代码更干净,还避免了变量被意外修改的风险。

4.1.2 if 的完整结构

当然,if-else if-else也是支持的:

val score = 85
val grade = if (score >= 90) {
    "优秀"
} else if (score >= 80) {
    "良好"
} else if (score >= 60) {
    "及格"
} else {
    "不及格"
}
println(grade) // 输出:良好

嗯,这里要注意:Kotlin没有三元运算符(就是Java里的 ? :)。因为if表达式已经能完美替代它了,语言设计者觉得没必要再加一个。

4.2 when:switch 的“究极进化”

如果你用过Java的switch,你一定吐槽过它:只能比较整数、枚举、字符串,还容易漏掉break。Kotlin的when,简直就是switch的完美形态。

4.2.1 基本用法

val x = 3
when (x) {
    1 -> println("x == 1")
    2 -> println("x == 2")
    3, 4 -> println("x == 3 或 4")  // 多个值用逗号分隔
    else -> println("其他值")
}

不需要break,不会穿透。每个分支自动独立,这省了多少心啊。

4.2.2 when 作为表达式

和if一样,when也可以有返回值:

val day = 3
val dayName = when (day) {
    1 -> "星期一"
    2 -> "星期二"
    3 -> "星期三"
    4 -> "星期四"
    5 -> "星期五"
    6, 7 -> "周末"
    else -> "非法日期"
}
println(dayName) // 输出:星期三

小技巧:如果when作为表达式使用,必须覆盖所有可能的情况,否则编译器会报错。所以else分支通常是必须的,除非你确信已经穷举了所有值。

4.2.3 when 的高级匹配

这才是when真正强大的地方——它不只是匹配值,还能匹配类型、范围、甚至任意条件:

val obj: Any = "Hello"

when (obj) {
    is String -> println("字符串,长度:${obj.length}")  // 智能类型转换
    is Int -> println("整数,值:$obj")
    !is List<*> -> println("不是列表")  // 支持取反
    else -> println("未知类型")
}

// 匹配范围
val score = 85
when (score) {
    in 90..100 -> println("优秀")
    in 80..89 -> println("良好")
    in 60..79 -> println("及格")
    else -> println("不及格")
}

// 匹配任意条件(甚至可以不用参数)
val x = 10
when {
    x < 0 -> println("负数")
    x % 2 == 0 -> println("偶数")
    else -> println("正奇数")
}

我曾经在解析网络请求结果时,用when来区分成功、失败、超时等不同状态,配合密封类(sealed class)使用,代码清晰得不得了。这个后面讲到密封类时再细说。

4.3 循环:for 和 while

循环这块,Kotlin做了不少减法,也做了不少加法。减法是把那些容易出错的写法去掉了,加法是引入了更现代的迭代方式。

4.3.1 for 循环

Kotlin的for循环,说白了就是“foreach”的增强版:

// 遍历数组
val array = intArrayOf(1, 2, 3, 4, 5)
for (item in array) {
    println(item)
}

// 遍历列表
val list = listOf("A", "B", "C")
for (item in list) {
    println(item)
}

// 带索引遍历
for (index in list.indices) {
    println("index[$index] = ${list[index]}")
}

// 同时获取索引和值
for ((index, value) in list.withIndex()) {
    println("$index -> $value")
}

注意,Kotlin没有Java那种 for (int i = 0; i < 10; i++) 的传统写法。取而代之的是区间和步长,这个我们马上讲。

避坑指南:我曾经在遍历集合时,试图在for循环里删除元素,结果抛出了ConcurrentModificationException。正确的做法是用迭代器的remove()方法,或者用filter等函数式操作。

4.3.2 while 和 do-while

这两个和Java基本一样,没什么好说的:

var i = 0
while (i < 5) {
    println(i)
    i++
}

var j = 0
do {
    println(j)
    j++
} while (j < 5)

嗯,这里唯一要注意的是:while循环的条件是在每次迭代开始前检查,do-while是在结束后检查。所以do-while至少会执行一次。

4.4 区间与步长:Kotlin的“独门武器”

区间(Range)是Kotlin引入的一个非常实用的概念。它本质上就是一个不可变的数字序列,用来表示一个范围。

4.4.1 创建区间

// 闭区间 [1, 10]
val range1 = 1..10

// 半开区间 [1, 10)
val range2 = 1 until 10

// 降序区间 [10, 1]
val range3 = 10 downTo 1

// 指定步长
val range4 = 1..10 step 2  // 1, 3, 5, 7, 9

为什么会有半开区间?你想想看,遍历数组时,索引是从0到length-1,用 0 until array.length 就正好,不用再写 i < array.length 了。

4.4.2 区间的高级用法

// 判断是否在区间内
val x = 5
println(x in 1..10)  // true
println(x !in 1..4)  // true

// 遍历带步长
for (i in 1..10 step 2) {
    print("$i ")  // 输出:1 3 5 7 9
}

// 降序遍历
for (i in 10 downTo 1 step 3) {
    print("$i ")  // 输出:10 7 4 1
}

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

实用场景:区间最常见的用途就是替代传统的for循环。比如你要执行5次某个操作,可以写成 repeat(5) { ... },或者 for (i in 1..5) { ... }。我个人更推荐用repeat,语义更清晰。

4.4.3 实战:用区间实现分页

我在项目中用区间做过一个分页加载的功能,代码非常简洁:

data class Page(val pageIndex: Int, val pageSize: Int = 20)

fun loadPages(totalCount: Int, pageSize: Int = 20) {
    val totalPages = (totalCount + pageSize - 1) / pageSize
    for (pageIndex in 1..totalPages) {
        val page = Page(pageIndex, pageSize)
        // 加载第pageIndex页的数据
        println("加载第${page.pageIndex}页,每页${page.pageSize}条")
    }
}

loadPages(95, 20)
// 输出:
// 加载第1页,每页20条
// 加载第2页,每页20条
// 加载第3页,每页20条
// 加载第4页,每页20条
// 加载第5页,每页20条

你看,用 1..totalPages 这个区间,直接就把分页逻辑表达清楚了。要是用Java,你得写个for循环,还得小心边界条件。

4.5 本章小结

这一章的内容,说白了就是Kotlin在控制流上的“语法糖”。但别小看这些糖,它们能让你写出的代码更短、更安全、更易读。

特性 Java Kotlin
条件判断 if是语句,有三元运算符 if是表达式,无三元运算符
多分支 switch,容易漏break when,无穿透,支持类型/范围匹配
循环 for (int i=0; i<n; i++) for (i in 1..n),区间迭代
范围表示 无原生支持 区间(Range),支持步长

下一章,我们会进入Kotlin的“面向对象”部分。到时候你会发现,Kotlin在类、接口、继承这些方面,也做了很多让人拍大腿的设计。咱们下章见。