8、高阶函数与Lambda:让代码像乐高一样灵活
说实话,刚接触Kotlin的时候,我对高阶函数和Lambda是有点懵的。那时候我还在写Java,习惯了匿名内部类那一套。直到有一次我在项目中重构一个网络请求的回调,发现用Lambda写出来简直不要太清爽。嗯,从那以后我就彻底爱上了这种写法。
这一章我们来聊聊高阶函数和Lambda。说白了,就是让函数也能像变量一样传来传去。你想想看,如果函数能作为参数,那代码的灵活性就完全不一样了。
- 高阶函数:接收函数作为参数,或返回一个函数
- Lambda表达式:一种简洁的函数字面量
- 函数类型:描述函数的签名(参数和返回值)
- 内联函数:消除Lambda带来的性能开销
- 常用高阶函数:map、filter、forEach等
8.1 高阶函数的概念
什么是高阶函数?简单说,就是把函数当作普通值来对待。你可以把它传给另一个函数,也可以从函数里把它返回出来。
我记得有一次在做一个数据清洗的工具,需要根据不同的规则过滤数据。如果用传统方式,我得写一堆if-else。但用了高阶函数,直接把过滤规则作为参数传进去,代码瞬间就优雅了。
// 一个简单的高阶函数
fun processNumbers(numbers: List<Int>, operation: (Int) -> Int): List<Int> {
val result = mutableListOf<Int>()
for (num in numbers) {
result.add(operation(num))
}
return result
}
// 使用
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = processNumbers(numbers) { it * 2 }
println(doubled) // [2, 4, 6, 8, 10]
看到没?operation这个参数的类型是(Int) -> Int,意思就是「接收一个Int,返回一个Int的函数」。这就是函数类型。
8.2 Lambda表达式
Lambda表达式,说白了就是没有名字的函数。它用花括号包裹,箭头左边是参数,右边是函数体。
// Lambda的基本语法
val sum: (Int, Int) -> Int = { x, y -> x + y }
// 如果只有一个参数,可以用it代替
val square: (Int) -> Int = { it * it }
// 调用Lambda
println(sum(3, 4)) // 7
println(square(5)) // 25
我个人习惯在Lambda参数比较多的时候,还是显式写出参数名,这样可读性更好。比如:
val calculate: (Int, Int, String) -> Int = { a, b, op ->
when (op) {
"+" -> a + b
"-" -> a - b
"*" -> a * b
else -> throw IllegalArgumentException("未知操作符")
}
}
list.map { it * 2 } 其实就是 list.map({ it * 2 }) 的简写。
8.3 函数类型
函数类型就是描述函数的样子。它的语法是:(参数类型) -> 返回值类型。
| 函数类型 | 说明 | 示例 |
|---|---|---|
() -> Unit |
无参数,无返回值 | { println("hello") } |
(Int) -> Int |
接收Int,返回Int | { it * 2 } |
(String, Int) -> Boolean |
接收String和Int,返回Boolean | { str, len -> str.length > len } |
(Int) -> (String) -> Boolean |
返回一个函数 | 高阶函数嵌套 |
你可能会问:「函数类型有什么用?」我举个例子。有一次我需要写一个缓存工具,缓存策略可以动态切换。用函数类型就特别方便:
class Cache<K, V>(private val strategy: (K) -> V) {
private val store = mutableMapOf<K, V>()
fun get(key: K): V {
return store.getOrPut(key) { strategy(key) }
}
}
// 使用不同的策略
val dbCache = Cache<Int, String> { id -> "从数据库查询: $id" }
val apiCache = Cache<Int, String> { id -> "从API获取: $id" }
8.4 内联函数
Lambda虽然好用,但有个问题:每次调用Lambda都会创建一个匿名对象。如果频繁调用,会有性能开销。这时候就需要内联函数出场了。
用inline关键字标记的函数,编译器会把函数体直接复制到调用处,省去了函数调用的开销。
inline fun measureTime(block: () -> Unit) {
val start = System.nanoTime()
block()
val end = System.nanoTime()
println("耗时: ${end - start} ns")
}
// 使用
measureTime {
Thread.sleep(100)
}
- Lambda被频繁调用(比如循环中)
- Lambda捕获了外部变量(内联可以避免对象创建)
- 需要非局部返回(在Lambda中使用return)
我曾经在一个实时数据处理的项目中,因为没注意内联,导致GC频繁触发。后来把关键路径上的高阶函数加上inline,性能提升了30%左右。嗯,这个坑我踩过,所以提醒你注意。
8.5 常用高阶函数
Kotlin标准库提供了很多现成的高阶函数。我挑几个最常用的说说。
map:转换集合中的每个元素
val numbers = listOf(1, 2, 3, 4, 5)
val squares = numbers.map { it * it }
println(squares) // [1, 4, 9, 16, 25]
// 也可以转换类型
val strings = numbers.map { "数字: $it" }
println(strings) // [数字: 1, 数字: 2, ...]
filter:筛选符合条件的元素
val numbers = listOf(1, 2, 3, 4, 5, 6)
val evens = numbers.filter { it % 2 == 0 }
println(evens) // [2, 4, 6]
// 可以链式调用
val result = numbers
.filter { it > 2 }
.map { it * 10 }
println(result) // [30, 40, 50, 60]
forEach:遍历每个元素
val names = listOf("Alice", "Bob", "Charlie")
names.forEach { name ->
println("Hello, $name!")
}
// 带索引的遍历
names.forEachIndexed { index, name ->
println("第${index + 1}位: $name")
}
我个人在实际项目中,最喜欢把filter和map链起来用。比如处理用户列表:
data class User(val name: String, val age: Int, val isActive: Boolean)
val users = listOf(
User("张三", 25, true),
User("李四", 17, false),
User("王五", 30, true),
User("赵六", 22, true)
)
// 获取所有活跃用户的姓名,年龄大于18的
val activeAdultNames = users
.filter { it.isActive && it.age >= 18 }
.map { it.name }
.sorted()
println(activeAdultNames) // [张三, 王五, 赵六]
filter的顺序,导致先map后filter,结果map改变了数据类型,filter的条件就不对了。记住:先过滤,再转换,这样效率更高,逻辑也更清晰。
8.6 知识体系图
下面这张图帮你理清本章的核心逻辑:
8.7 实战:用高阶函数重构代码
最后,我分享一个真实案例。之前有个需求:从一堆日志中提取错误信息,并按级别分类。用传统方式写出来又长又乱。用高阶函数重构后,代码清晰多了:
data class LogEntry(val level: String, val message: String, val timestamp: Long)
val logs = listOf(
LogEntry("ERROR", "数据库连接失败", 1000),
LogEntry("WARN", "内存使用率过高", 2000),
LogEntry("INFO", "用户登录成功", 3000),
LogEntry("ERROR", "空指针异常", 4000)
)
// 传统方式
fun getErrorMessagesOld(logs: List<LogEntry>): List<String> {
val result = mutableListOf<String>()
for (log in logs) {
if (log.level == "ERROR") {
result.add(log.message)
}
}
return result
}
// 高阶函数方式
fun getErrorMessagesNew(logs: List<LogEntry>): List<String> {
return logs
.filter { it.level == "ERROR" }
.map { it.message }
.sorted()
}
// 按级别分组
val grouped = logs.groupBy { it.level }
println(grouped)
// {ERROR=[LogEntry(level=ERROR, ...)], WARN=[...], INFO=[...]}
你看,代码量减少了一半,而且意图一目了然。这就是高阶函数的魅力。
好了,这一章的内容就到这里。高阶函数和Lambda是Kotlin的精华所在,掌握了它们,你的代码会变得更简洁、更灵活。多写多练,慢慢就会形成肌肉记忆。
本章要点回顾:
- 高阶函数让函数成为「一等公民」
- Lambda表达式是简洁的函数字面量
- 函数类型定义了函数的签名
- 内联函数优化Lambda的性能
- map、filter、forEach是日常开发利器