Skip to content

Lambda 与高阶函数

源:High-order functions and lambdas

在 Kotlin 中,函数是“一等公民”。这意味着函数可以存储在变量中、作为参数传递给其他函数,或者作为其他函数的返回值。这种特性是函数式编程(FP)的基石。

函数类型 (Function Types)

要像变量一样处理函数,首先需要描述它的“类型”。函数类型由参数列表和返回值类型组成。

kotlin
// 格式: (参数类型列表) -> 返回值类型
val onClick: (Int) -> Unit = ...
val comparator: (String, String) -> Int = ...
val provider: () -> Any = ...

可空函数类型

函数类型本身也可以是可空的,但需要注意括号的位置。

kotlin
// 变量本身可空(即 onClick 可能是 null)
val onClick: ((Int) -> Unit)? = null

// 这是一个返回 Int? 的函数,变量本身不可空
val measure: () -> Int? = ...

Lambda 表达式

Lambda 表达式是实例化函数类型最简洁的方式。它本质上是一个匿名的小段代码块

语法解剖

kotlin
val sum: (Int, Int) -> Int = { x: Int, y: Int ->
    println("Calculating $x + $y")
    x + y // 最后一行为返回值
}
  1. 大括号包裹:Lambda 始终在 {} 中。
  2. 参数声明:在 -> 之前。如果类型可推断,可省略类型标注。
  3. 函数体:在 -> 之后。
  4. 隐式返回:Lambda 的最后一个表达式自动成为返回值(严禁使用 return 关键字返回结果)。

简写规则

Kotlin 提供了极极致的语法糖来简化 Lambda 调用:

kotlin
// 如果 Lambda 只有一个参数,可省略声明,直接用 it 代表
list.filter { it > 0 } 
// 等价于: list.filter { x -> x > 0 }
kotlin
// 如果函数的最后一个参数是 Lambda,可将其移出括号
thread(start = true) { 
    doWork() 
}
kotlin
// 如果不使用某个参数,用 _ 代替,避免命名污染
map.forEach { (_, value) -> 
    println(value) 
}
kotlin
// 直接拆解复合对象(如 Pair)
map.mapValues { (key, value) -> 
    "$key-$value" 
}

匿名函数 (Anonymous Functions)

虽然 Lambda 很方便,但它有两个局限:

  1. 无法显式指定返回值类型(只能靠推断)。
  2. 控制流 (return) 行为特殊。

当需要更精细的控制时,可以使用匿名函数:

kotlin
val filter = fun(x: Int): Boolean {
    if (x == 0) return false // 这里的 return 是局部的
    return x > 0
}

Lambda vs 匿名函数:Return 的区别

  • Lambda: return非局部返回(Non-local return),它会直接退出包含该 Lambda 的外部函数(除非 Lambda 是内联的)。
  • 匿名函数: return局部返回,仅退出该匿名函数本身,类似于普通的函数调用。

高阶函数 (Higher-Order Functions)

高阶函数是指接受函数作为参数返回函数的函数。这是 Kotlin 标准库(如 filter, map)的核心实现方式。

kotlin
// 接受一个函数作为参数
fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try {
        return body() // 执行传入的函数
    } finally {
        lock.unlock()
    }
}

带接收者的函数类型 (Function Types with Receiver) 核心难点

这是理解 Kotlin DSL 和作用域函数(apply, with)的关键。

Kotlin 允许定义一种特殊的函数类型,它在调用时需要一个接收者对象 (Receiver)

语法对比

  • 普通函数类型:(A, B) -> C
  • 带接收者的类型:A.(B) -> C (读作:在 A 上调用,参数为 B,返回 C)

实例化与调用

这种 Lambda 内部拥有一个隐式的 this,指向接收者对象。

kotlin
// 定义:这个 Lambda 必须在 String 上执行
val shout: String.() -> Unit = {
    // 这里的 this 是 String 实例
    println(this.uppercase() + "!!!")
}

// 调用方式 1:扩展调用语法(推荐)
"hello".shout()

// 调用方式 2:作为第一个参数传递
shout("hello")

为什么这很重要?

当你看到 html { body { ... } } 这种代码时,body 之所以能直接调用,就是因为 lambda 的类型是 HTML.() -> Unit,当前的上下文( this)被切换成了 HTML 对象。

闭包与变量捕获

Lambda 可以访问其闭包(定义时的外部作用域)中的变量。与 Java 不同,Kotlin 的 Lambda 可以修改捕获的非 final 变量。

kotlin
var sum = 0
ints.filter { it > 0 }.forEach {
    sum += it // 直接修改外部变量,无需 Wrapper
}

总结

  • 类型系统(Int) -> String 是合法的类型。
  • 语法糖:熟练使用 it、尾随 Lambda 和解构。
  • 接收者String.() -> Unit 让 Lambda 内部拥有了 this,这是 DSL 的魔法之源。
  • 控制流:注意 Lambda 中 return 的非局部性。