前几天同事问我:为什么会有高阶函数这个东西?难道还有低级函数吗?
我笑而不语,回道:等我!
何为高阶
高阶函数指:如果函数本身可以作为另一个函数的参数,或者可以作为一个函数的返回值,或二者兼而有之,那么这个函数,我们就称之为高阶函数。
你有没有发现?这和变量已经一样了!
它让 API 更灵活、更利于表达「按行为配置」的逻辑,是 Kotlin 里函数式风格的支柱;常见用途包括回调、数据变换,以及对集合做 map、filter、reduce 等操作。
语法
当函数的参数类型或返回类型被声明为函数类型时,它就具备了高阶函数的形态。函数类型的写法是 (参数类型) -> 返回类型。
fun higherOrderFunction(input: Int, operation: (Int) -> Int): Int {
return operation(input)
}
这里的 operation 就是一个「入参为 Int、出参为 Int」的函数。
当然,参数可以是多个,例如 (Int, String) -> Int。
高阶函数适合在调用时传入不同行为,我们看看函数作为参数怎么调用。例如:
fun double(x: Int): Int {
return x * 2
}
fun main() {
val result = higherOrderFunction(5, ::double)
println(result) // Output: 10
}
::double 以函数引用的形式传入,并在内部作用在输入上。
当然,也可以让高阶函数返回一个函数,例如按名字选择运算:
fun operation(type: String): (Int, Int) -> Int {
return when (type) {
"add" -> { a, b -> a + b }
"multiply" -> { a, b -> a * b }
else -> { _, _ -> 0 }
}
}
fun main() {
val addOperation = operation("add")
println(addOperation(3, 4)) // Output: 7
}
这里的 operation 会根据传入的 type 动态创建并返回对应的函数。
常见用途
- 集合上的 lambda:标准库里的
map、filter、reduce都建立在高阶函数之上。
val numbers = listOf(1, 2, 3, 4)
val doubled = numbers.map { it * 2 }
println(doubled) // Output: [2, 4, 6, 8]
- 事件与回调:完成通知、异步收尾等常写成「接收一个函数」的形式。
fun performAction(onComplete: () -> Unit) {
println("Performing action...")
onComplete()
}
fun main() {
performAction { println("Action completed!") }
}
优势
Kotlin 里你可以把函数当作普通值传递、返回和保存,由此得到:
- 复用性:行为与「主流程」拆开,同一套骨架可接不同函数,实现不同效果。
- 可读性:尤其在集合场景下,
map/filter/reduce比手写循环更能直接表达「在做什么」。 - 灵活性:运行时可以替换、组合行为,适合事件、回调、UI 逻辑等。
- 抽象层次:便于把控制流、惯用法、算法泛化,写出跨类型、跨场景复用的工具。
小结
高阶函数与 lambda 搭配,让 Kotlin 在保持简洁的同时,能写出清晰、偏函数式且可维护的代码。
这里需要注意的是,lambda 只是向高阶函数传递行为的一种常见方式,正如上文提到的 ::double 这种函数引用写法,同样也能作为参数传递给高阶函数。
进阶:JVM 的落地
嘻嘻,又到了我们最喜欢的 Java 字节码环节了。
对于高阶函数的字节码,Kotlin 会做固定几类变换,使函数式特性在 JVM 上可用且尽量高效。
1. 函数式接口
高阶函数在字节码里体现为 kotlin.jvm.functions.* 下的接口,例如:
Function0<R>:无参;Function1<P, R>:单参;Function2<P1, P2, R>:双参;依此类推。
例如:
fun higherOrderExample(operation: (Int) -> Int): Int {
return operation(10)
}
大致会对应到接受 Function1<Integer, Integer> 的方法,并通过 invoke 调用:
int higherOrderExample(Function1<Integer, Integer> operation) {
return operation.invoke(10);
}
2. Lambda 变成匿名类
传入的 lambda 通常会变成实现对应函数式接口的匿名类,概念上类似:
val result = higherOrderExample { it * 2 }
Function1<Integer, Integer> lambda = new Function1<Integer, Integer>() {
@Override
public Integer invoke(Integer it) {
return it * 2;
}
};
int result = higherOrderExample(lambda);
3. invokedynamic
编译器可利用 Java 7 引入的 invokedynamic 指令,让 JVM 在运行时(通常借助 LambdaMetafactory)动态生成 lambda 的实现类,从而取代在编译期生成大量匿名内部类的做法。
这种机制带来了两点明显的好处:
- 减小产物体积:编译后生成的
.class文件数量显著减少(特别是在大型项目中)。 - 优化运行时性能:将 lambda 实例化的策略交由 JVM 底层决策,JVM 可以做更深度的优化(例如复用无捕获状态的 lambda 实例)。
注:自 Kotlin 1.5 起,针对 JVM 目标平台,编译器已经默认使用 invokedynamic 来编译普通的 lambda 表达式。
4. inline
若高阶函数标成 inline,lambda 体可能被直接抄进调用点,不再为本次调用单独建函数对象。例如:
inline fun inlineHigherOrderExample(operation: (Int) -> Int): Int {
return operation(10)
}
val result = inlineHigherOrderExample { it * 2 }
在效果上可理解为直接得到类似 10 * 2 的字节码,从而减少分配与间接调用。
int result = 10 * 2;
归纳
- 高阶函数 → 形参/返回值用
FunctionN等接口表达。 - Lambda → 匿名类实现接口,或走
invokedynamic等动态生成。 - inline → 把 lambda 体内联到调用处,降低对象分配与方法调用开销。
弄清这些有助于在 JVM 目标下做性能取舍(例如何时值得用 inline)。
一点想法
高阶函数为 Kotlin 带来了极其强大的表达力和简洁性,但由于底层的 FunctionN 和对象分配,如果不加控制也容易引入隐形的性能开销。
在实际开发中:
- 遇到被频繁调用的高阶函数(例如集合遍历、协程挂起等核心库),果断配合
inline关键字使用以消除闭包和对象创建成本。 - 而在普通回调(如点击事件响应等低频操作)中,直接使用普通的高阶函数即可,Kotlin 1.5+ 默认开启的
invokedynamic会帮忙把运行效率和包体积都控制得很好。