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("未知操作符")
    }
}
小技巧:如果Lambda是函数的最后一个参数,可以把它移到括号外面。这就是Kotlin的「尾Lambda语法」。比如 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捕获了外部变量(内联可以避免对象创建)
  • 需要非局部返回(在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")
}

我个人在实际项目中,最喜欢把filtermap链起来用。比如处理用户列表:

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的顺序,导致先mapfilter,结果map改变了数据类型,filter的条件就不对了。记住:先过滤,再转换,这样效率更高,逻辑也更清晰。

8.6 知识体系图

下面这张图帮你理清本章的核心逻辑:

高阶函数与Lambda 高阶函数概念 Lambda表达式 函数类型 内联函数 尾Lambda语法 参数与返回值描述 消除Lambda开销 常用高阶函数 map · filter · forEach · reduce · flatMap · groupBy 函数作为一等公民 · 代码更灵活

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是日常开发利器

公众号:蓝海资料掘金营,微信deep3321