2、高阶函数:从定义到实战

各位同学,今天我们来聊聊高阶函数。说实话,这个概念刚接触时我也觉得有点绕,但用顺手了就会发现——它其实是 Kotlin 最性感的特性之一。

2.1 什么是高阶函数?

先给个官方定义:高阶函数就是接收函数作为参数,或者返回一个函数的函数。说白了,函数也能像变量一样传来传去。

我刚开始学的时候,脑子里总有个疑问:函数不就是一段代码吗?怎么还能当参数?后来在项目里写回调写吐了,才明白高阶函数有多香。

核心要点:在 Kotlin 中,函数是一等公民。你可以把它赋值给变量,塞进集合,甚至从另一个函数里蹦出来。

2.2 函数类型——给函数一个“身份证”

要让函数当参数,首先得有个类型来描述它。Kotlin 用 (参数类型) -> 返回类型 这种语法来表示函数类型。

// 一个接收 Int 返回 String 的函数类型
(Int) -> String

// 两个参数,返回 Boolean
(Int, String) -> Boolean

// 没有参数,返回 Unit(相当于 void)
() -> Unit

我在项目中见过有人把函数类型写得很长,比如 (Context, String, Int) -> View。嗯,这时候我建议用 typealias 给它起个短名:

typealias ViewBuilder = (Context, String, Int) -> View

2.3 Lambda 表达式——最常用的函数字面量

Lambda 就是一段可以传递的代码块。语法很简单:{ 参数 -> 函数体 }

val sum: (Int, Int) -> Int = { x, y -> x + y }

// 调用
println(sum(3, 5))  // 输出 8

你想想看,如果没有 Lambda,你得先定义一个函数,再传引用,多麻烦。Lambda 让代码变得特别紧凑。

小技巧:如果 Lambda 只有一个参数,可以用 it 代替。比如 { it.length } 等价于 { str -> str.length }。我个人习惯在参数简单时用 it,复杂时还是显式命名更清晰。

2.4 匿名函数——另一种写法

Lambda 虽然简洁,但有时候你想显式声明返回类型,或者有多个 return 点,这时候匿名函数就派上用场了。

// Lambda 写法
val list = listOf(1, 2, 3, 4, 5)
val evens = list.filter { it % 2 == 0 }

// 匿名函数写法
val evens2 = list.filter(fun(x: Int): Boolean {
    return x % 2 == 0
})

说实话,我平时 90% 的情况都用 Lambda。匿名函数用得少,但遇到复杂逻辑时它能让代码意图更明确。

2.5 高阶函数作为参数

这是高阶函数最经典的应用场景。比如 Android 里常见的点击监听:

fun setOnClickListener(listener: (View) -> Unit) {
    // ...
}

// 调用时
button.setOnClickListener { view ->
    Toast.makeText(this, "点击了", Toast.LENGTH_SHORT).show()
}

我曾经在项目里封装过一个网络请求工具,用高阶函数处理成功和失败回调:

fun requestData(
    onSuccess: (Data) -> Unit,
    onError: (Throwable) -> Unit
) {
    // 网络请求...
    if (success) {
        onSuccess(data)
    } else {
        onError(exception)
    }
}

// 调用
requestData(
    onSuccess = { data -> showData(data) },
    onError = { error -> showError(error) }
)

2.6 高阶函数作为返回值

函数还能返回函数?没错。这在实现策略模式或者工厂模式时特别有用。

fun getCalculator(type: String): (Int, Int) -> Int {
    return when (type) {
        "add" -> { a, b -> a + b }
        "sub" -> { a, b -> a - b }
        "mul" -> { a, b -> a * b }
        else -> { _, _ -> 0 }
    }
}

// 使用
val addFunc = getCalculator("add")
println(addFunc(10, 5))  // 输出 15

我记得有一次做计算器功能,用这种方式把运算逻辑抽离出来,后续加新运算只需要在 when 分支里加一行,维护起来特别爽。

2.7 内联函数(inline)——性能优化利器

高阶函数虽然好用,但有个隐藏问题:每次传 Lambda 都会创建匿名内部类对象。如果频繁调用,GC 压力会变大。

inline 关键字就是来解决这个问题的。它会把函数调用处的代码直接“复制粘贴”过去,避免对象创建。

inline fun measureTime(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    val end = System.currentTimeMillis()
    println("耗时:${end - start}ms")
}

// 调用
measureTime {
    // 一些耗时操作
    Thread.sleep(100)
}

加了 inline 后,编译器会把 measureTime 的代码和 Lambda 的代码直接展开到调用处,不会生成额外的 Function 对象。

注意:inline 不是银弹。如果 Lambda 很大,或者函数被多次调用,内联会导致代码膨胀。我一般只在工具类函数、或者高频调用的场景用 inline。

2.8 noinline 与 crossinline

这两个是 inline 的补充。先说 noinline:当你 inline 一个函数时,所有 Lambda 参数默认都会被内联。但如果你想把某个 Lambda 当作对象传递(比如存到集合里),就需要用 noinline 标记它。

inline fun test(
    block1: () -> Unit,   // 会被内联
    noinline block2: () -> Unit  // 保持为对象
) {
    block1()
    val list = listOf(block2)  // noinline 才能这样用
}

再说 crossinline:它用于标记 Lambda 不能有非局部 return。什么意思呢?就是 Lambda 里不能直接 return 外层函数。

inline fun runInRunnable(crossinline block: () -> Unit) {
    Runnable {
        // 这里不能直接 return,因为 Runnable 的上下文不同
        block()  // 如果 block 里有 return,编译会报错
    }.run()
}

我曾经踩过这个坑:在 Lambda 里写了个 return,结果编译报错,查了半天才发现是 crossinline 的限制。嗯,后来就记住了——如果 Lambda 要在另一个上下文中执行,就用 crossinline

2.9 知识体系总览

下面这张图帮你理清本章的核心脉络:

高阶函数 接收函数作为参数 返回函数 函数类型:(Int) -> String Lambda 表达式 匿名函数 inline 内联函数 noinline crossinline 作为参数:回调、监听器 作为返回值:策略模式

2.10 避坑指南

最后分享几个我踩过的坑:

  • Lambda 中的 return 问题:在 Lambda 里写 return,默认会返回外层函数(如果函数是 inline 的)。想只返回 Lambda 本身,要用 return@label 语法。
  • 内联大函数:别把几百行的函数加上 inline,生成的字节码会爆炸。我一般只 inline 10 行以内的工具函数。
  • noinline 与 crossinline 混用:如果一个 Lambda 既要被存储(noinline),又要在其他线程执行(crossinline),两个标记都要加。

总结一下:高阶函数让 Kotlin 的函数式编程变得优雅。理解函数类型、Lambda、内联机制,你就能写出更简洁、更灵活的 Android 代码。下一章我们会用这些知识来构建 DSL,那才是真正展现 Kotlin 魅力的地方。


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