4Kotlin in Action

Lambda编程

函数式编程

Lambda高阶函数内联函数接收者

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第4章:Lambda编程(Lambda Programming)

一、导读

1.1 本章学习目标

第4章是 Kotlin 函数式编程的核心。Lambda 表达式和高阶函数是 Kotlin 表达力的重要来源,它们让你能够用更简洁、更声明式的方式处理集合、事件和异步操作。

本章的核心学习目标包括:

  • 掌握 Lambda 表达式的完整语法:参数、返回类型、it 隐式参数
  • 理解高阶函数:接收函数作为参数或返回函数的函数
  • 掌握内联函数(inline)的原理和使用场景
  • 学会使用带接收者的 Lambda(lambda with receiver)构建 DSL
  • 掌握集合的函数式操作:filter、map、flatMap、reduce 等
  • 理解 Kotlin 中函数类型的底层实现

1.2 为什么本章如此重要

Lambda 编程是 Kotlin 与 Java 7 及之前版本最大的差异之一。虽然 Java 8 引入了 Lambda 表达式,但 Kotlin 的 Lambda 更加强大和灵活——它与扩展函数、内联函数、类型别名等特性深度集成,使得构建 DSL、处理集合、编写异步代码都变得更加自然。

更重要的是,Kotlin 的标准库大量使用高阶函数。filter、map、flatMap、groupBy、associate 等函数构成了 Kotlin 集合操作的基础。掌握这些函数式操作,你的代码将变得更加简洁、可读和可维护。

1.3 核心问题引导

Lambda 表达式在底层是如何实现的?它有性能开销吗?

什么是高阶函数?为什么 inline 关键字对高阶函数如此重要?

it 隐式参数在什么情况下可以使用?什么时候应该避免使用?

什么是"带接收者的 Lambda"?它如何使得 DSL 成为可能?

Kotlin 的集合函数式操作与 Java Stream API 有什么区别?


二、核心概念详解

2.1 Lambda 表达式基础

2.1.1 Lambda 语法

kotlin
// 完整语法
val sum: (Int, Int) -> Int = { a: Int, b: Int -> a + b }

// 类型推断
val sum2 = { a: Int, b: Int -> a + b }

// 使用函数类型
val sum3: (Int, Int) -> Int = { a, b -> a + b }

// 调用
println(sum(1, 2))    // 3

2.1.2 it 隐式参数

当 Lambda 只有一个参数时,可以省略参数声明,使用 it 引用该参数:

kotlin
val numbers = listOf(1, 2, 3, 4, 5)

// 使用 it
val doubled = numbers.map { it * 2 }
val evens = numbers.filter { it % 2 == 0 }

// 等价于显式参数
val doubled2 = numbers.map { n -> n * 2 }

it 让简单的 Lambda 非常简洁,但在嵌套 Lambda 中应该避免使用 it,改用显式参数名以提高可读性。

2.1.3 Lambda 与集合

kotlin
val people = listOf(
    Person("Alice", 30),
    Person("Bob", 25),
    Person("Charlie", 35)
)

// 查找
val oldest = people.maxByOrNull { it.age }

// 过滤
val over28 = people.filter { it.age > 28 }

// 映射
val names = people.map { it.name }

// 分组
val byAgeGroup = people.groupBy { person ->
    when {
        person.age < 30 -> "Young"
        person.age < 35 -> "Middle"
        else -> "Senior"
    }
}

// 链式操作
val result = people
    .filter { it.age >= 30 }
    .sortedByDescending { it.age }
    .joinToString(", ") { it.name }

2.2 高阶函数

2.2.1 接收函数作为参数

kotlin
// 一个简单的高阶函数
fun <T> Collection<T>.customFilter(predicate: (T) -> Boolean): List<T> {
    val result = mutableListOf<T>()
    for (item in this) {
        if (predicate(item)) {
            result.add(item)
        }
    }
    return result
}

val numbers = listOf(1, 2, 3, 4, 5)
val evens = numbers.customFilter { it % 2 == 0 }

2.2.2 返回函数

kotlin
fun createMultiplier(factor: Int): (Int) -> Int {
    return { number -> number * factor }
}

val double = createMultiplier(2)
val triple = createMultiplier(3)

println(double(5))    // 10
println(triple(5))    // 15

2.2.3 函数类型

kotlin
// 函数类型声明
typealias Predicate<T> = (T) -> Boolean
typealias Mapper<T, R> = (T) -> R
typealias Comparator<T> = (T, T) -> Int

fun <T> List<T>.find(predicate: Predicate<T>): T? {
    for (item in this) {
        if (predicate(item)) return item
    }
    return null
}

2.3 内联函数(inline)

2.3.1 为什么需要 inline

高阶函数有一个潜在的性能问题:每次调用都会创建一个 Lambda 对象。对于频繁调用的小 Lambda,这个开销可能不可忽略。

inline 关键字告诉编译器将函数体和 Lambda 体直接内联到调用点,消除对象创建和方法调用的开销:

kotlin
inline fun <T> List<T>.customForEach(block: (T) -> Unit) {
    for (item in this) {
        block(item)
    }
}

// 编译后等价于:
// for (item in list) {
//     println(item)    // Lambda 体被内联到这里
// }

2.3.2 noinline 和 crossinline

kotlin
inline fun foo(
    bar: () -> Unit,         // 会被内联
    noinline baz: () -> Unit  // 不会被内联
) {
    // bar 的调用点会被内联
    // baz 作为函数对象传递
}

// crossinline 用于在内联函数中调用 Lambda,但不在 Lambda 中直接返回
inline fun <T> Iterable<T>.locked(lock: Lock, body: crossinline () -> T): T {
    lock.lock()
    try {
        return body()
    } finally {
        lock.unlock()
    }
}

2.3.3 非局部返回

内联函数的 Lambda 中可以使用 return 返回外层函数(非局部返回):

kotlin
inline fun <T> List<T>.findAndProcess(predicate: (T) -> Boolean, processor: (T) -> Unit) {
    for (item in this) {
        if (predicate(item)) {
            processor(item)
            return    // 非局部返回:从 findAndProcess 返回
        }
    }
}

// 使用
numbers.findAndProcess(
    { it > 5 },
    { 
        println("Found: $it")
        return    // 从调用 findAndProcess 的外层函数返回!
    }
)

2.4 带接收者的 Lambda

2.4.1 基本概念

带接收者的 Lambda 是 Kotlin DSL 的核心机制:

kotlin
// 函数类型:StringBuilder.() -> Unit
// 接收者类型是 StringBuilder
fun buildString(block: StringBuilder.() -> Unit): String {
    val sb = StringBuilder()
    sb.block()    // 在 StringBuilder 上调用 Lambda
    return sb.toString()
}

val result = buildString {
    append("Hello")
    append(" ")
    append("World")
    // 这里的 this 是 StringBuilder
}

2.4.2 with、apply、also、let、run

Kotlin 标准库提供了五个作用域函数,它们都是带接收者的 Lambda 的应用:

kotlin
val person = Person("Alice", 30)

// let: it 引用对象,返回 Lambda 结果
person.let {
    println("Name: ${it.name}, Age: ${it.age}")
    it.name.uppercase()
}

// apply: this 引用对象,返回对象本身
val mutablePerson = Person("Bob", 25).apply {
    // 修改属性(如果是 var)
}

// run: this 引用对象,返回 Lambda 结果
val description = person.run {
    "$name is $age years old"
}

// with: 非扩展函数,this 引用参数
val result = with(person) {
    "$name is $age years old"
}

// also: it 引用对象,返回对象本身
val logged = person.also {
    println("Created: $it")
}

2.5 集合的函数式操作

2.5.1 转换操作

kotlin
val numbers = listOf(1, 2, 3, 4, 5)

// map: 一对一转换
val doubled = numbers.map { it * 2 }    // [2, 4, 6, 8, 10]

// flatMap: 一对多转换,然后展平
val words = listOf("Hello", "World")
val chars = words.flatMap { it.toList() }    // [H, e, l, l, o, W, o, r, l, d]

// mapNotNull: 转换并过滤 null
val parsed = listOf("1", "abc", "3").mapNotNull { it.toIntOrNull() }    // [1, 3]

// associate: 转换为 Map
val nameToAge = people.associateBy({ it.name }, { it.age })

// zip: 合并两个集合
val names = listOf("Alice", "Bob")
val ages = listOf(30, 25)
val paired = names.zip(ages)    // [(Alice, 30), (Bob, 25)]

2.5.2 过滤操作

kotlin
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// filter: 保留满足条件的元素
val evens = numbers.filter { it % 2 == 0 }

// filterNot: 保留不满足条件的元素
val odds = numbers.filterNot { it % 2 == 0 }

// take / drop
val first3 = numbers.take(3)       // [1, 2, 3]
val rest = numbers.drop(3)         // [4, 5, 6, 7, 8, 9, 10]

// partition: 分成两组
val (evens2, odds2) = numbers.partition { it % 2 == 0 }

2.5.3 聚合操作

kotlin
val numbers = listOf(1, 2, 3, 4, 5)

// reduce: 累积
val sum = numbers.reduce { acc, n -> acc + n }    // 15

// fold: 带初始值的 reduce
val product = numbers.fold(1) { acc, n -> acc * n }    // 120

// 其他聚合
val count = numbers.count()
val max = numbers.maxOrNull()
val min = numbers.minOrNull()
val average = numbers.average()
val any = numbers.any { it > 3 }      // true
val all = numbers.all { it > 0 }      // true
val none = numbers.none { it < 0 }    // true

三、实战案例

3.1 案例一:构建 HTML DSL

kotlin
@DslMarker
annotation class HtmlDsl

@HtmlDsl
open class Tag(val name: String) {
    private val children = mutableListOf<Tag>()
    private val attributes = mutableListOf<String>()
    
    operator fun String.invoke(value: Any) {
        attributes.add("$this=\"$value\"")
    }
    
    fun tag(name: String, block: Tag.() -> Unit = {}): Tag {
        val tag = Tag(name)
        tag.block()
        children.add(tag)
        return tag
    }
    
    fun render(): String {
        val attrs = if (attributes.isEmpty()) "" else " " + attributes.joinToString(" ")
        return if (children.isEmpty()) {
            "<$name$attrs/>"
        } else {
            "<$name$attrs>${children.joinToString("") { it.render() }}</$name>"
        }
    }
}

@HtmlDsl
class Table : Tag("table") {
    fun tr(block: Tag.() -> Unit) = tag("tr", block)
    fun td(block: Tag.() -> Unit) = tag("td", block)
    fun th(block: Tag.() -> Unit) = tag("th", block)
}

fun table(block: Table.() -> Unit): String {
    val table = Table()
    table.block()
    return table.render()
}

fun main() {
    val html = table {
        tr {
            th { +"Name" }
            th { +"Age" }
        }
        tr {
            td { +"Alice" }
            td { +"30" }
        }
        tr {
            td { +"Bob" }
            td { +"25" }
        }
    }
    println(html)
}

3.2 案例二:实现一个简单的响应式流

kotlin
class Observable<T> {
    private val listeners = mutableListOf<(T) -> Unit>()
    
    fun subscribe(listener: (T) -> Unit) {
        listeners.add(listener)
    }
    
    fun emit(value: T) {
        listeners.forEach { it(value) }
    }
    
    fun <R> map(transform: (T) -> R): Observable<R> {
        val result = Observable<R>()
        subscribe { value ->
            result.emit(transform(value))
        }
        return result
    }
    
    fun filter(predicate: (T) -> Boolean): Observable<T> {
        val result = Observable<T>()
        subscribe { value ->
            if (predicate(value)) {
                result.emit(value)
            }
        }
        return result
    }
}

fun <T> observableOf(vararg values: T): Observable<T> {
    val obs = Observable<T>()
    values.forEach { obs.emit(it) }
    return obs
}

fun main() {
    val numbers = Observable<Int>()
    
    numbers
        .filter { it % 2 == 0 }
        .map { it * it }
        .subscribe { println("Result: $it") }
    
    numbers.emit(1)    // 过滤掉
    numbers.emit(2)    // Result: 4
    numbers.emit(3)    // 过滤掉
    numbers.emit(4)    // Result: 16
}

3.3 案例三:使用 fold 实现复杂的数据聚合

kotlin
data class SaleItem(val category: String, val amount: Double, val quantity: Int)

fun main() {
    val sales = listOf(
        SaleItem("Electronics", 999.99, 2),
        SaleItem("Books", 29.99, 5),
        SaleItem("Electronics", 499.99, 1),
        SaleItem("Books", 19.99, 3),
        SaleItem("Clothing", 59.99, 4)
    )
    
    // 按类别汇总
    val summary = sales.fold(mutableMapOf<String, Pair<Double, Int>>()) { acc, sale ->
        acc.merge(sale.category, sale.amount to sale.quantity) { existing, new ->
            (existing.first + new.first) to (existing.second + new.second)
        }
        acc
    }
    
    summary.forEach { (category, data) ->
        println("$category: Total=${data.first}, Quantity=${data.second}")
    }
}

四、本章小结

第4章深入讲解了 Kotlin 的 Lambda 编程:

Lambda 表达式:Kotlin 的 Lambda 语法简洁,支持 it 隐式参数。

高阶函数:接收或返回函数的函数,是函数式编程的基础。

内联函数:inline 消除高阶函数的性能开销,支持非局部返回。

带接收者的 Lambda:Kotlin DSL 的核心机制,使得类型安全的构建器模式成为可能。

集合函数式操作:map、filter、flatMap、fold、groupBy 等函数构成了声明式数据处理的基础。


五、思考与练习

思考题

inline 函数的 Lambda 参数和非 inline 函数的 Lambda 参数在字节码层面有什么区别?

crossinline 和 noinline 分别在什么场景下使用?为什么 crossinline 禁止非局部返回?

Kotlin 的集合函数式操作是立即求值的(eager),这在处理大数据集时有什么问题?如何解决?

带接收者的 Lambda 和普通 Lambda 在类型系统上有什么区别?

编程练习

实现一个 repeat 函数,接收一个次数 n 和一个 Lambda,执行 Lambda n 次。

使用 fold 实现一个函数,将 List<String> 转换为一个格式化的表格字符串。

构建一个简单的配置 DSL,支持 database { host = "..."; port = 3306 } 这样的语法。


4/8Lamb
0%