第 2 篇|Kotlin 进阶 ------ 集合、循环与条件表达式
掌握 Kotlin 集合框架与流程控制,写出简洁高效的 Kotlin 风格代码
哈喽,各位坚持学习的小伙伴们!上一篇我们搞定了 Kotlin 的变量、函数和空安全机制,今天正式进入 Kotlin 进阶篇。这一篇,我们将解锁 Kotlin 中最具生产力的三大核心技能:集合操作、流程控制(条件与循环)、以及让你代码瞬间变优雅的高阶函数。
如果你是从 Java 转过来的,你一定会惊叹:原来操作一个列表可以只用一行链式调用!原来 switch 可以被when 秒成渣!话不多说,我们直接上硬货。
一、Kotlin 集合全景图:可变与不可变,泾渭分明
Kotlin 的集合框架在设计上做了一个非常清晰的切割,这是它区别于 Java 集合的最大亮点之一:
- 不可变集合(只读集合):List、Set、Map。一旦创建,不能增删改元素,天然线程安全,编译期就能拦截误修改。
- 可变集合(可读写集合):MutableList、MutableSet、MutableMap。可以自由增删改元素。
这种设计从根源上避免了并发修改异常,也让代码意图一目了然。
1. 集合类型概览
| 集合类型 | 不可变版本 | 可变版本 | 特点 |
|---|---|---|---|
| 列表 | List | MutableList | 有序、可重复 |
| 集合 | Set | MutableSet | 无序、不可重复 |
| 映射 | Map | MutableMap | 键值对存储,键唯一 |
2. List(列表)------ 有序可重复
kotlin
fun main() {
// 不可变 List:无法修改
val readOnlyList = listOf("Apple", "Banana", "Orange")
// readOnlyList.add("Grape") // ❌ 编译报错!Unresolved reference: add
// 可变 MutableList:可以随意增删改
val mutableList = mutableListOf("Apple", "Banana")
mutableList.add("Orange") // ✅ 正确
mutableList.removeAt(0) // ✅ 正确
println(mutableList) // 输出:[Banana, Orange]
}
3. Set(集合)------ 无序不可重复
kotlin
fun main() {
// 不可变 Set,自动去重
val readOnlySet = setOf(1, 2, 3, 2, 1)
println(readOnlySet) // 输出:[1, 2, 3]
// 可变 MutableSet
val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.add(4)
println(mutableSet) // 输出:[1, 2, 3, 4]
}
4. Map(映射/字典)------ 键值对
Kotlin 中创建 Map 的语法非常优雅,使用 to 关键字连接键和值(to 是一个中缀函数,返回 Pair 对象)。
kotlin
fun main() {
// 不可变 Map
val readOnlyMap = mapOf(
"name" to "张三",
"age" to 18,
"city" to "北京"
)
println(readOnlyMap["name"]) // 输出:张三
// 可变 MutableMap
val mutableMap = mutableMapOf("score" to 90)
mutableMap["level"] = "优秀" // 添加新键值对
mutableMap["score"] = 95 // 修改已有键的值
println(mutableMap) // 输出:{score=95, level=优秀}
// 遍历 Map(解构声明)
for ((key, value) in readOnlyMap) {
println("$key -> $value")
}
}
二、为什么选择 Kotlin 集合框架?核心优势深度解析
Kotlin 集合框架并非对 Java 集合的简单封装,而是从设计层面进行了全面优化,解决了 Java 集合的诸多痛点。
1. 明确的可变性控制:从根源避免并发问题
Java 集合的最大痛点之一是可变性模糊:即使你用 Collections.unmodifiableList() 包装,也只是在运行时抛出异常,编译期无法感知。
Java 反面教材:
java
// Java:编译期无法阻止修改,运行时才报错
List<String> list = new ArrayList<>();
list.add("Apple");
List<String> unmodifiableList = Collections.unmodifiableList(list);
unmodifiableList.add("Banana"); // 编译通过!运行时抛出 UnsupportedOperationException
Kotlin 优雅解决:
kotlin
// Kotlin:编译期直接报错,从根源避免问题
val list = listOf("Apple", "Banana")
// list.add("Orange") // 编译报错:Unresolved reference 'add'
// 需要修改时,明确使用可变版本
val mutableList = mutableListOf("Apple", "Banana")
mutableList.add("Orange") // 正常执行
2. 内置空安全:彻底告别 NullPointerException
Kotlin 集合框架深度集成了空安全特性,让你在编译期就能发现潜在的空指针问题。
kotlin
// 1. 明确区分"可空元素的集合"与"不可空元素的集合"
val listWithNulls: List<String?> = listOf("Apple", null, "Orange") // 允许包含 null
val listWithoutNulls: List<String> = listOf("Apple", "Banana") // 不允许包含 null
// 2. 安全访问元素
val firstItem = listWithNulls.firstOrNull() // 安全获取,可能为 null
// 3. 便捷的过滤 null 操作
val nonNullList = listWithNulls.filterNotNull() // 自动过滤 null,结果:[Apple, Orange]
3. 丰富的高阶函数:一行代码完成复杂数据处理
这是 Kotlin 集合最强大的优势,没有之一。通过 filter、map、reduce 等高阶函数,能以声明式的方式处理数据,代码可读性提升数倍。
需求:从学生列表中找出及格的男生,按分数降序排列,提取姓名列表。
Java 版本(15+ 行代码):
java
// Java:繁琐的循环、临时变量、条件判断
List<Student> students = ...;
List<Student> filtered = new ArrayList<>();
for (Student s : students) {
if (s.getScore() >= 60 && "男".equals(s.getGender())) {
filtered.add(s);
}
}
Collections.sort(filtered, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return Integer.compare(s2.getScore(), s1.getScore());
}
});
List<String> names = new ArrayList<>();
for (Student s : filtered) {
names.add(s.getName());
}
Kotlin 版本(1 行链式调用):
kotlin
// Kotlin:声明式、链式调用,逻辑清晰
val names = students
.filter { it.score >= 60 && it.gender == "男" }
.sortedByDescending { it.score }
.map { it.name }
4. 极简的工厂方法:告别 new 与 add 的冗余
Java 创建集合需要先 new 再 add,代码冗余;Kotlin 提供了极简的工厂方法,一行代码完成创建与初始化。
kotlin
// Java 繁琐初始化 vs Kotlin 极简工厂方法
val list = listOf("Apple", "Banana", "Orange")
val map = mapOf("Alice" to 25, "Bob" to 30)
// 甚至支持生成式初始化
val evenNumbers = List(10) { it * 2 } // 生成 [0, 2, 4, ..., 18]
5. 完美的 Java 互操作性:无缝迁移,零成本接入
Kotlin 集合框架基于 Java 集合进行封装,因此与 Java 代码完全互操作。你可以在 Kotlin 中直接使用 Java 集合,也可以在 Java 中直接使用 Kotlin 集合,现有 Java 项目可以逐步迁移到 Kotlin。
kotlin
// Kotlin 中调用 Java 方法
val javaList = JavaClass.getJavaList() // 返回 Java 的 List
javaList.forEach { println(it) } // 可以直接用 Kotlin 高阶函数
// Java 中调用 Kotlin 方法
List<String> kotlinList = KotlinClass.getKotlinList(); // 返回 Kotlin 的 List
kotlinList.add("New Item"); // 可以直接用 Java 方法修改
三、集合创建语法:极简写法告别冗余
Kotlin 提供了极其简洁的集合创建方式,无需像 Java 那样先 new 再 add。
1. List 与 Set 创建
kotlin
// 不可变集合
val list = listOf(1, 2, 3)
val set = setOf("Kotlin", "Java", "Python")
// 可变集合
val mutableList = mutableListOf<String>()
val mutableSet = mutableSetOf(10, 20, 30)
// 空集合
val emptyList = emptyList<Int>()
val emptySet = emptySet<String>()
// 过滤 null 的集合
val listNotNull = listOfNotNull(1, null, 3, null) // 结果:[1, 3]
2. Map 创建:键值对的优雅写法
kotlin
// 不可变 Map
val map = mapOf(
"name" to "Alice",
"age" to 25,
"city" to "Beijing"
)
// 可变 Map
val mutableMap = mutableMapOf<String, Any>()
mutableMap["name"] = "Bob"
mutableMap["age"] = 30
四、条件表达式:告别冗余,拥抱表达式思维
Kotlin 的 if 和 when 不仅是语句,更是表达式(可返回值),这是与 Java 最大的区别之一。
1. if 作为表达式:替代三元运算符
Java 中常用 (condition) ? a : b,Kotlin 直接用 if 表达式实现,更灵活:
kotlin
val a = 10
val b = 20
// Kotlin 风格:if 表达式直接返回值
val max = if (a > b) a else b
// 多分支 if 表达式(块内最后一行是返回值)
val score = 85
val result = if (score >= 90) {
"优秀"
} else if (score >= 80) {
"良好"
} else if (score >= 60) {
"及格"
} else {
"不及格"
}
println(result) // 输出:良好
2. when 表达式:强大的模式匹配(替代 switch)
Kotlin 的 when 比 Java 的 switch 强大得多,支持值匹配、类型匹配、范围匹配,甚至无参数模式。
(1)值匹配(替代 switch)
kotlin
val day = 3
val dayName = when (day) {
1 -> "周一"
2 -> "周二"
3 -> "周三"
4 -> "周四"
5 -> "周五"
else -> "周末" // 必须有 else(作为表达式时)
}
println(dayName) // 输出:周三
(2)多值匹配与范围匹配
kotlin
val score = 75
val level = when (score) {
in 90..100 -> "优"
in 80 until 90 -> "良"
in 60..79 -> "中"
in 0 until 60 -> "差"
else -> "无效分数"
}
// 多值合并匹配
val number = 5
val type = when (number) {
0, 2, 4, 6, 8 -> "偶数"
1, 3, 5, 7, 9 -> "奇数"
else -> "未知"
}
(3)类型匹配(智能转换)
kotlin
fun processValue(value: Any) {
when (value) {
is String -> println("字符串长度:${value.length}") // 智能转换为 String
is Int -> println("整数平方:${value * value}") // 智能转换为 Int
is List<*> -> println("列表大小:${value.size}")
else -> println("未知类型")
}
}
processValue("Kotlin") // 输出:字符串长度:6
processValue(5) // 输出:整数平方:25
(4)无参数 when(替代 if-else 链)
当判断条件不是同一个值时,可省略 when 的参数:
kotlin
val age = 25
val hasLicense = true
val canDrive = when {
age < 18 -> "未成年,不能开车"
!hasLicense -> "成年但无驾照"
else -> "可以开车"
}
println(canDrive) // 输出:可以开车
⚠️ 新手必踩坑点 ①:when 表达式遗漏分支,导致逻辑异常
kotlin
// 错误示例
fun getLevel(score: Int): String {
return when (score) {
in 90..100 -> "优"
in 80..89 -> "良"
in 60..79 -> "中"
// 遗漏了 else 分支!
}
}
// 编译报错:'when' expression must be exhaustive
正确写法:when 作为表达式时,必须覆盖所有可能的情况(或加 else 分支)。
kotlin
fun getLevel(score: Int): String {
return when (score) {
in 90..100 -> "优"
in 80..89 -> "良"
in 60..79 -> "中"
else -> "差" // 覆盖剩余情况
}
}
五、循环遍历:for-in、forEach 与区间的灵活运用
Kotlin 提供了多种循环方式,结合区间(Range)和高阶函数,遍历逻辑更简洁。
1. for-in 循环:遍历集合与区间
kotlin
// 遍历 List
val fruits = listOf("Apple", "Banana", "Orange")
for (fruit in fruits) {
println(fruit)
}
// 遍历索引(withIndex)
for ((index, fruit) in fruits.withIndex()) {
println("索引 $index:$fruit")
}
// 遍历 Map(解构声明)
val userMap = mapOf("name" to "Alice", "age" to 25)
for ((key, value) in userMap) {
println("$key -> $value")
}
2. 区间(Range):...、until、downTo、step
Kotlin 用区间表示连续的数值范围,是循环的利器:
| 写法 | 含义 | 示例输出 |
|---|---|---|
| 1...5 | 闭区间 [1, 5] | 1 2 3 4 5 |
| 1 until 5 | 左闭右开 [1, 5) | 1 2 3 4 |
| 5 downTo 1 | 降序闭区间 [5, 1] | 5 4 3 2 1 |
| 1...10 step 2 | 带步长 | 1 3 5 7 9 |
kotlin
// 闭区间
for (i in 1..5) print("$i ") // 输出:1 2 3 4 5
// 半开区间
for (i in 1 until 5) print("$i ") // 输出:1 2 3 4
// 倒序区间
for (i in 5 downTo 1) print("$i ") // 输出:5 4 3 2 1
// 步长为 2
for (i in 1..10 step 2) print("$i ") // 输出:1 3 5 7 9
3. forEach 高阶函数:函数式遍历
forEach 是集合的扩展函数,结合 Lambda 表达式,遍历更简洁:
kotlin
val numbers = listOf(1, 2, 3, 4, 5)
// 普通 forEach
numbers.forEach { println(it) } // it 是默认参数名
// 带索引的 forEachIndexed
numbers.forEachIndexed { index, num ->
println("索引 $index:$num")
}
// Map 的 forEach
val map = mapOf("a" to 1, "b" to 2)
map.forEach { (key, value) -> println("$key -> $value") }
六、集合高阶函数:filter/map/find/sortedBy 链式调用实战
Kotlin 集合的核心优势在于高阶函数,通过 filter、map、find 等函数的链式调用,能以极少的代码完成复杂的数据处理。
1. 常用高阶函数详解
| 函数名 | 作用 | 返回值类型 |
|---|---|---|
| filter | 过滤符合条件的元素 | List/Set |
| map | 对每个元素进行转换 | List |
| find / firstOrNull | 查找第一个符合条件的元素(找不到返回 null) | T? |
| sortedBy | 按指定字段排序(升序) | List |
| sortedByDescending | 按指定字段排序(降序) | List |
| groupBy | 按条件分组,返回 Map | Map |
| any | 判断是否至少有一个元素符合条件 | Boolean |
| all | 判断是否所有元素都符合条件 | Boolean |
| count | 统计满足条件的元素个数 | Int |
| maxByOrNull | 按条件查找最大值对应的元素 | T? |
2. 链式调用实战:处理学生数据
先定义一个数据类 Student:
kotlin
data class Student(
val name: String,
val score: Int,
val gender: String
)
创建学生列表并进行链式处理:
kotlin
val students = listOf(
Student("Alice", 85, "女"),
Student("Bob", 58, "男"),
Student("Charlie", 92, "男"),
Student("Diana", 76, "女"),
Student("Eric", 60, "男")
)
// 需求:找出所有及格(>=60)的男生,按分数降序排列,提取姓名列表
val result = students
.filter { it.score >= 60 } // 过滤及格
.filter { it.gender == "男" } // 过滤男生
.sortedByDescending { it.score } // 按分数降序
.map { it.name } // 提取姓名
println(result) // 输出:[Charlie, Eric]
// 其他常用操作
val avgScore = students.filter { it.score >= 60 }.map { it.score }.average()
val topStudent = students.maxByOrNull { it.score }
val hasFailed = students.any { it.score < 60 }
println("及格平均分:$avgScore")
println("最高分学生:${topStudent?.name}")
println("是否有不及格:$hasFailed")
七、新手必踩坑点清单
坑点 ①:向不可变集合添加元素,触发编译报错
错误示例:
kotlin
val list = listOf(1, 2, 3)
list.add(4) // 编译报错:Unresolved reference 'add'
原因:listOf() 返回的是 List 接口(只读),没有 add 方法。
正确写法:
kotlin
// 用 mutableListOf 创建可变集合
val mutableList = mutableListOf(1, 2, 3)
mutableList.add(4) // 正常执行
// 或通过 toMutableList() 转换
val list = listOf(1, 2, 3)
val newList = list.toMutableList()
newList.add(4)
坑点 ②:when 表达式遗漏分支,导致编译报错或逻辑异常
上文已详述,此处不再赘述。记住一句话:when 作为表达式时,必须穷尽所有可能,或添加 else 兜底
八、综合小案例:学生成绩管理器控制台程序
让我们把今天学的所有知识点串联起来,开发一个功能完整的学生成绩管理器。
需求说明
- 定义学生列表(包含姓名、成绩)
- 过滤及格分数(≥60)
- 计算及格学生的平均分
- 对每个学生评级(优/良/中/差)
- 找出并打印最高分学生
完整代码实现
kotlin
// 1. 定义学生数据类
data class Student(
val name: String,
val score: Int
)
fun main() {
// 2. 创建学生列表
val students = listOf(
Student("Alice", 95),
Student("Bob", 55),
Student("Charlie", 82),
Student("Diana", 73),
Student("Eric", 60),
Student("Fiona", 45)
)
println("========== 学生成绩管理器 ==========\n")
// 3. 过滤及格学生
val passedStudents = students.filter { it.score >= 60 }
println("✅ 及格学生名单(共 ${passedStudents.count()} 人):")
passedStudents.forEach { println(" ${it.name}:${it.score}分") }
// 4. 计算及格平均分
val avgScore = passedStudents.map { it.score }.average()
println("\n📊 及格平均分:${
String.format("%.2f", avgScore)
}")
// 5. 学生评级
println("\n🏆 学生成绩评级:")
students.forEach { student ->
val level = when (student.score) {
in 90..100 -> "优"
in 80 until 90 -> "良"
in 60..79 -> "中"
else -> "差"
}
println(" ${student.name} (${student.score}分) -> $level")
}
// 6. 找出最高分学生
val topStudent = students.maxByOrNull { it.score }
topStudent?.let {
println("\n🥇 最高分得主:${it.name},分数:${it.score}分")
}
// 7. 额外:统计各等级人数
val gradeCount = students.groupingBy { student ->
when (student.score) {
in 90..100 -> "优"
in 80 until 90 -> "良"
in 60..79 -> "中"
else -> "差"
}
}.eachCount()
println("\n📈 各等级人数统计:")
gradeCount.forEach { (grade, count) ->
println(" $grade : $count 人")
}
println("\n=====================================")
}
运行结果预览
text
========== 学生成绩管理器 ==========
✅ 及格学生名单(共 4 人):
Alice:95分
Charlie:82分
Diana:73分
Eric:60分
📊 及格平均分:77.50
🏆 学生成绩评级:
Alice (95分) -> 优
Bob (55分) -> 差
Charlie (82分) -> 良
Diana (73分) -> 中
Eric (60分) -> 中
Fiona (45分) -> 差
🥇 最高分得主:Alice,分数:95分
📈 各等级人数统计:
优 : 1 人
差 : 2 人
良 : 1 人
中 : 2 人
=====================================
案例技术点总结
- filter 过滤及格学生
- map 提取分数并调用 average() 计算均值
- when 表达式进行分数评级
- maxByOrNull 安全地查找最高分
- groupingBy 配合 eachCount 进行分组统计
- 全程使用安全调用与 Elvis 运算符,无 !!
九、总结与下篇预告
恭喜你又啃下了一块硬骨头!今天我们系统学习了 Kotlin 进阶的核心技能:
- ✅ Kotlin 集合框架的 5 大核心优势:可变性控制、空安全、高阶函数、极简语法、Java 互操作
- ✅ 集合创建:分清 listOf 和 mutableListOf,默认优先不可变
- ✅ 条件表达式:if 和 when 都能作为表达式直接返回值,when 功能尤其强大
- ✅ 区间与循环:...、until、downTo、step,遍历集合花样百出
- ✅ 高阶函数链式调用:filter、map、sortedBy 让你的代码从"怎么做"变成"做什么"
- ✅ 避坑指南:不可变集合无法修改、when 表达式必须穷尽分支
这些知识是后续学习 Android UI 开发、数据展示、网络请求处理的基石。尤其是集合高阶函数,在实际开发中的使用频率极高,一定要多敲代码,形成肌肉记忆。
💡 学习建议:尝试把今天案例中的学生数据换成你自己的测试数据,自己动手改写一下链式调用的顺序,看看效果有什么不同。编程最好的老师就是亲自试错。
✨ 如果觉得本文对你有帮助,欢迎点赞、收藏、分享给一起学 Android 的小伙伴!
下一篇我们将正式踏入 Android 世界的大门,讲解 Android 项目结构与第一个界面开发,手把手带你做出第一个能跑在手机上的 App!不见不散!