【Kotlin系列12】函数式编程在Kotlin中的实践:从Lambda到函数组合的优雅之旅

0 阅读9分钟

引言:命令式vs函数式

假设你需要处理一个用户列表,筛选出成年用户并按年龄排序:

// 命令式编程:告诉计算机"怎么做"
fun filterAndSortUsers(users: List<User>): List<User> {
    val adults = mutableListOf<User>()

    // 第1步:筛选成年人
    for (user in users) {
        if (user.age >= 18) {
            adults.add(user)
        }
    }

    // 第2步:排序
    for (i in 0 until adults.size - 1) {
        for (j in i + 1 until adults.size) {
            if (adults[i].age > adults[j].age) {
                val temp = adults[i]
                adults[i] = adults[j]
                adults[j] = temp
            }
        }
    }

    return adults
}

函数式编程:告诉计算机"做什么"

// 函数式编程:声明式、链式调用
fun filterAndSortUsers(users: List<User>): List<User> =
    users
        .filter { it.age >= 18 }
        .sortedBy { it.age }

对比结果:

  • 代码量:从20行减少到3行
  • 可读性:意图一目了然
  • 可维护性:易于修改和扩展
  • 错误率:减少手动循环带来的bug

本文将带你全面掌握Kotlin的函数式编程特性。

函数式编程核心概念

第一等公民(First-Class Functions)

在Kotlin中,函数是第一等公民,可以:

// 1. 赋值给变量
val greet: (String) -> String = { name -> "Hello, $name!" }

// 2. 作为参数传递
fun executeOperation(x: Int, y: Int, operation: (Int, Int) -> Int): Int {
    return operation(x, y)
}

val sum = executeOperation(5, 3) { a, b -> a + b }  // 8
val product = executeOperation(5, 3) { a, b -> a * b }  // 15

// 3. 作为返回值
fun getMathOperation(type: String): (Int, Int) -> Int {
    return when (type) {
        "add" -> { a, b -> a + b }
        "multiply" -> { a, b -> a * b }
        else -> { a, b -> 0 }
    }
}

val addOp = getMathOperation("add")
println(addOp(10, 5))  // 15

纯函数(Pure Functions)

纯函数特点:

  1. 相同输入总是产生相同输出
  2. 没有副作用(不修改外部状态)
// ✅ 纯函数
fun add(a: Int, b: Int): Int = a + b

// ✅ 纯函数
fun double(numbers: List<Int>): List<Int> =
    numbers.map { it * 2 }  // 不修改原列表

// ❌ 非纯函数(有副作用)
var count = 0
fun incrementAndReturn(): Int {
    count++  // 修改外部状态
    return count
}

// ❌ 非纯函数(随机结果)
fun randomInt(): Int = Random.nextInt()

不可变性(Immutability)

// 推荐:使用不可变集合
val numbers = listOf(1, 2, 3)
val doubled = numbers.map { it * 2 }  // 返回新列表

// 避免:可变集合
val mutableNumbers = mutableListOf(1, 2, 3)
mutableNumbers.add(4)  // 修改原列表

高阶函数

高阶函数:接收函数作为参数或返回函数的函数。

12-01-higher-order-functions.png

常见高阶函数

// 1. filter - 筛选
val numbers = listOf(1, 2, 3, 4, 5, 6)
val evens = numbers.filter { it % 2 == 0 }  // [2, 4, 6]

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

// 3. reduce - 聚合
val sum = numbers.reduce { acc, n -> acc + n }  // 21

// 4. fold - 带初始值的聚合
val product = numbers.fold(1) { acc, n -> acc * n }  // 720

// 5. forEach - 遍历(有副作用)
numbers.forEach { println(it) }

// 6. takeWhile - 取满足条件的前缀
val prefix = numbers.takeWhile { it < 4 }  // [1, 2, 3]

// 7. groupBy - 分组
val grouped = numbers.groupBy { it % 2 == 0 }
// {false=[1, 3, 5], true=[2, 4, 6]}

自定义高阶函数

// 示例1:repeat高阶函数
fun repeat(times: Int, action: (Int) -> Unit) {
    for (i in 1..times) {
        action(i)
    }
}

repeat(3) { index ->
    println("执行第 $index 次")
}
// 输出:
// 执行第 1 次
// 执行第 2 次
// 执行第 3 次

// 示例2:测量执行时间
fun <T> measureTime(block: () -> T): Pair<T, Long> {
    val start = System.currentTimeMillis()
    val result = block()
    val duration = System.currentTimeMillis() - start
    return result to duration
}

val (result, time) = measureTime {
    (1..1000000).sum()
}
println("结果: $result, 耗时: ${time}ms")

Lambda表达式

Lambda语法

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

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

// 单参数可以用it
val double: (Int) -> Int = { it * 2 }

// 多行Lambda
val complex = { x: Int ->
    val squared = x * x
    val doubled = squared * 2
    doubled  // 最后一行是返回值
}

// 无参数Lambda
val greet = { println("Hello!") }

Lambda作为最后参数

当Lambda是最后一个参数时,可以移到括号外:

// 常规写法
numbers.filter({ it % 2 == 0 })

// Lambda在括号外(推荐)
numbers.filter { it % 2 == 0 }

// 多个参数
numbers.fold(0, { acc, n -> acc + n })

// 最后一个是Lambda,移出去
numbers.fold(0) { acc, n -> acc + n }

带接收者的Lambda

// StringBuilder的apply是带接收者的Lambda
val html = StringBuilder().apply {
    append("<html>")
    append("<body>")
    append("Hello")
    append("</body>")
    append("</html>")
}.toString()

// 等价于
val html2 = buildString {
    append("<html>")
    append("<body>")
    append("Hello")
    append("</body>")
    append("</html>")
}

扩展函数

基础用法

// 为String添加扩展函数
fun String.isPalindrome(): Boolean {
    return this == this.reversed()
}

println("level".isPalindrome())  // true
println("hello".isPalindrome())  // false

// 为List添加扩展
fun <T> List<T>.second(): T? {
    return if (this.size >= 2) this[1] else null
}

val numbers = listOf(1, 2, 3)
println(numbers.second())  // 2

扩展属性

// 只读扩展属性
val String.lastChar: Char
    get() = this[length - 1]

println("Hello".lastChar)  // o

// 可变扩展属性(必须有backing field)
var StringBuilder.lastChar: Char
    get() = this[length - 1]
    set(value) {
        this.setCharAt(length - 1, value)
    }

val sb = StringBuilder("Hello")
sb.lastChar = '!'
println(sb)  // Hell!

扩展函数的作用域

class MyClass {
    // 类内部扩展函数
    private fun String.shout() = this.uppercase() + "!"

    fun demo() {
        println("hello".shout())  // HELLO!
    }
}

// "hello".shout()  // ❌ 外部不可见

标准库函数

let、run、with、apply、also

12-02-scope-functions.png

data class Person(var name: String, var age: Int)

// 1. let - 参数为it,返回Lambda结果
val nameLength = Person("Alice", 25).let {
    println(it.name)  // 使用it访问
    it.name.length    // 返回值
}  // 5

// 2. run - 参数为this,返回Lambda结果
val ageAfter10Years = Person("Bob", 30).run {
    age += 10         // 直接访问属性
    age               // 返回值
}  // 40

// 3. with - 非扩展函数,参数为this
val description = with(Person("Charlie", 35)) {
    "$name is $age years old"  // 直接访问
}  // "Charlie is 35 years old"

// 4. apply - 参数为this,返回接收者
val person = Person("David", 40).apply {
    age = 41          // 修改属性
    name = "Dave"
}  // 返回修改后的person对象

// 5. also - 参数为it,返回接收者
val samePerson = Person("Eve", 45).also {
    println("Created: ${it.name}")  // 使用it
}  // 返回原对象

选择建议

函数使用场景
let对可空对象执行操作,变量作用域限定
run对象配置+计算结果
with对一个对象执行多个操作
apply对象初始化、配置(Builder模式)
also附加操作(日志、验证)

takeIf与takeUnless

// takeIf - 条件满足返回自己,否则返回null
val evenNumber = 42.takeIf { it % 2 == 0 }  // 42
val oddNumber = 43.takeIf { it % 2 == 0 }   // null

// takeUnless - 条件不满足返回自己
val notZero = 5.takeUnless { it == 0 }      // 5
val zero = 0.takeUnless { it == 0 }         // null

// 实用示例
fun processInput(input: String?) {
    input
        ?.takeIf { it.isNotBlank() }
        ?.let { println("处理: $it") }
        ?: println("输入为空")
}

内联函数

inline关键字

// 没有inline
fun normalHigherOrder(block: () -> Unit) {
    println("Before")
    block()
    println("After")
}

// 有inline
inline fun inlinedHigherOrder(block: () -> Unit) {
    println("Before")
    block()
    println("After")
}

// 调用
normalHigherOrder { println("Body") }
inlinedHigherOrder { println("Body") }

编译后的代码

// normalHigherOrder编译后(伪代码)
Function0 function = new Function0() {
    public void invoke() {
        println("Body");
    }
};
normalHigherOrder(function);  // 创建Function对象

// inlinedHigherOrder编译后(伪代码)
println("Before");
println("Body");  // Lambda内容直接内联
println("After");  // 无需创建Function对象

内联的好处

  1. 避免对象创建:不创建Function对象
  2. 支持非局部返回:可以从Lambda中直接return
  3. 性能提升:减少函数调用开销
// 非内联:不能从Lambda中return
fun normalForEach(list: List<Int>, action: (Int) -> Unit) {
    for (item in list) {
        action(item)
    }
}

fun demo1() {
    normalForEach(listOf(1, 2, 3)) {
        if (it == 2) return  // ❌ 编译错误
        println(it)
    }
}

// 内联:支持非局部返回
inline fun inlineForEach(list: List<Int>, action: (Int) -> Unit) {
    for (item in list) {
        action(item)
    }
}

fun demo2() {
    inlineForEach(listOf(1, 2, 3)) {
        if (it == 2) return  // ✅ 从demo2函数返回
        println(it)
    }
    println("不会执行")
}

noinline与crossinline

// noinline - 阻止特定参数内联
inline fun mixed(
    inlined: () -> Unit,
    noinline notInlined: () -> Unit
) {
    inlined()
    notInlined()
    saveForLater(notInlined)  // 需要存储,不能内联
}

// crossinline - 禁止非局部返回
inline fun runInAnotherThread(crossinline block: () -> Unit) {
    thread {
        block()  // 在另一个线程,不能非局部返回
    }
}

函数组合

12-03-function-composition.png

基础组合

// 函数组合:f(g(x))
infix fun <A, B, C> ((B) -> C).compose(g: (A) -> B): (A) -> C {
    return { a -> this(g(a)) }
}

val addOne: (Int) -> Int = { it + 1 }
val double: (Int) -> Int = { it * 2 }

// 组合:先double,再addOne
val doubleThenAddOne = addOne compose double
println(doubleThenAddOne(5))  // 11  ((5 * 2) + 1)

// 管道:先addOne,再double
infix fun <A, B, C> ((A) -> B).pipe(g: (B) -> C): (A) -> C {
    return { a -> g(this(a)) }
}

val addOneThenDouble = addOne pipe double
println(addOneThenDouble(5))  // 12  ((5 + 1) * 2)

柯里化(Currying)

// 普通函数
fun add(a: Int, b: Int, c: Int): Int = a + b + c

// 柯里化版本
fun curriedAdd(a: Int): (Int) -> (Int) -> Int {
    return { b ->
        { c ->
            a + b + c
        }
    }
}

val add5 = curriedAdd(5)
val add5And3 = add5(3)
val result = add5And3(2)  // 10

// 简化写法
val result2 = curriedAdd(5)(3)(2)  // 10

偏函数应用(Partial Application)

// 偏函数应用
fun <A, B, C> ((A, B) -> C).partial(a: A): (B) -> C {
    return { b -> this(a, b) }
}

val multiply: (Int, Int) -> Int = { a, b -> a * b }
val multiplyBy5 = multiply.partial(5)

println(multiplyBy5(3))  // 15
println(multiplyBy5(7))  // 35

函数式集合操作

链式调用

data class User(val name: String, val age: Int, val city: String)

val users = listOf(
    User("Alice", 28, "北京"),
    User("Bob", 32, "上海"),
    User("Charlie", 25, "北京"),
    User("David", 35, "深圳"),
    User("Eve", 30, "上海")
)

// 复杂的链式操作
val result = users
    .filter { it.age >= 30 }           // 筛选30岁以上
    .map { it.name to it.city }        // 转换为姓名-城市对
    .groupBy { it.second }             // 按城市分组
    .mapValues { (_, pairs) ->         // 提取姓名列表
        pairs.map { it.first }
    }

println(result)
// {上海=[Bob, Eve], 深圳=[David]}

Sequence惰性求值

// List:立即求值(Eager)
val list = listOf(1, 2, 3, 4, 5)
val result1 = list
    .map {
        println("map: $it")
        it * 2
    }
    .filter {
        println("filter: $it")
        it > 5
    }
// 输出:
// map: 1
// map: 2
// map: 3
// map: 4
// map: 5
// filter: 2
// filter: 4
// filter: 6
// filter: 8
// filter: 10

// Sequence:惰性求值(Lazy)
val sequence = sequenceOf(1, 2, 3, 4, 5)
val result2 = sequence
    .map {
        println("map: $it")
        it * 2
    }
    .filter {
        println("filter: $it")
        it > 5
    }
    .toList()  // 终端操作才执行
// 输出:
// map: 1
// filter: 2
// map: 2
// filter: 4
// map: 3
// filter: 6
// map: 4
// map: 5

// 性能对比
val hugeList = (1..1_000_000).toList()

// List:会创建中间集合
val time1 = measureTimeMillis {
    hugeList
        .map { it * 2 }
        .filter { it > 1000 }
        .take(10)
}

// Sequence:只处理需要的元素
val time2 = measureTimeMillis {
    hugeList.asSequence()
        .map { it * 2 }
        .filter { it > 1000 }
        .take(10)
        .toList()
}

println("List: ${time1}ms, Sequence: ${time2}ms")
// List: 150ms, Sequence: 2ms

实战案例:DSL构建

HTML DSL

// DSL定义
@DslMarker
annotation class HtmlDsl

@HtmlDsl
class HTML {
    private val elements = mutableListOf<String>()

    fun head(block: Head.() -> Unit) {
        val head = Head().apply(block)
        elements.add("<head>${head.build()}</head>")
    }

    fun body(block: Body.() -> Unit) {
        val body = Body().apply(block)
        elements.add("<body>${body.build()}</body>")
    }

    fun build() = "<html>${elements.joinToString("")}</html>"
}

@HtmlDsl
class Head {
    private val elements = mutableListOf<String>()

    fun title(text: String) {
        elements.add("<title>$text</title>")
    }

    fun build() = elements.joinToString("")
}

@HtmlDsl
class Body {
    private val elements = mutableListOf<String>()

    fun h1(text: String) {
        elements.add("<h1>$text</h1>")
    }

    fun p(text: String) {
        elements.add("<p>$text</p>")
    }

    fun build() = elements.joinToString("")
}

// DSL使用
fun html(block: HTML.() -> Unit): String {
    return HTML().apply(block).build()
}

// 生成HTML
val page = html {
    head {
        title("我的页面")
    }
    body {
        h1("欢迎")
        p("这是一个段落")
    }
}

println(page)
// <html><head><title>我的页面</title></head><body><h1>欢迎</h1><p>这是一个段落</p></body></html>

常见陷阱与最佳实践

❌ 陷阱1:过度使用函数式

// ❌ 过度函数式(可读性差)
val result = users
    .filter { it.age > 18 }
    .map { it.name }
    .filter { it.startsWith("A") }
    .map { it.uppercase() }
    .filter { it.length > 5 }

// ✅ 合并filter
val result = users
    .filter { it.age > 18 && it.name.startsWith("A") }
    .map { it.name.uppercase() }
    .filter { it.length > 5 }

❌ 陷阱2:忽略性能

// ❌ List:多次遍历
(1..1000000)
    .map { it * 2 }
    .filter { it > 1000 }
    .take(10)

// ✅ Sequence:惰性求值
(1..1000000).asSequence()
    .map { it * 2 }
    .filter { it > 1000 }
    .take(10)
    .toList()

✅ 最佳实践1:优先使用标准库函数

// ❌ 手动实现
fun sumOfSquares(numbers: List<Int>): Int {
    var sum = 0
    for (n in numbers) {
        sum += n * n
    }
    return sum
}

// ✅ 使用标准库
fun sumOfSquares(numbers: List<Int>): Int =
    numbers.sumOf { it * it }

✅ 最佳实践2:函数命名清晰

// ❌ 命名不清
val f: (Int) -> Int = { it * 2 }

// ✅ 命名清晰
val double: (Int) -> Int = { it * 2 }
val isEven: (Int) -> Boolean = { it % 2 == 0 }

小结

本文全面介绍了Kotlin的函数式编程特性:

核心概念

  1. 第一等函数:函数可赋值、传递、返回
  2. 高阶函数:接收或返回函数的函数
  3. Lambda表达式:简洁的匿名函数语法
  4. 纯函数:无副作用,相同输入产生相同输出

关键技能

  1. 扩展函数:为已有类添加新功能
  2. 标准库函数:let、run、with、apply、also
  3. 内联函数:性能优化,支持非局部返回
  4. 函数组合:compose、pipe、柯里化

最佳实践

  • ✅ 优先使用标准库函数
  • ✅ 使用Sequence处理大数据
  • ✅ 合理使用inline优化性能
  • ✅ 函数命名清晰表达意图
  • ✅ 避免过度函数式(保持可读性)

下期预告

下一篇文章将进入第三阶段"高级应用",讲解DSL设计:构建类型安全的领域语言

练习题

基础练习

练习1:实现一个通用的retry函数

// TODO: 失败时重试指定次数
fun <T> retry(times: Int, block: () -> T): T

练习2:实现一个带缓存的函数

// TODO: 缓存函数计算结果
fun <A, R> ((A) -> R).memoize(): (A) -> R

进阶练习

练习3:实现一个简单的验证DSL

// 期望用法:
val validation = validate<User> {
    User::name must notBeBlank()
    User::age must beInRange(0, 150)
    User::email must matchPattern(emailRegex)
}

练习4:实现函数管道运算符

// TODO: 实现管道运算符
infix fun <A, B> A.pipe(f: (A) -> B): B

// 使用:
val result = 5 pipe { it + 1 } pipe { it * 2 }  // 12

参考资料


系列文章导航:


如果这篇文章对你有帮助,欢迎点赞、收藏、分享!有任何问题或建议,欢迎在评论区留言讨论。让我们一起学习,一起成长!

也欢迎访问我的个人主页发现更多宝藏资源