很多人第一次听到"高阶函数"这个词,觉得很高级。
其实定义特别简单:接受函数作为参数,或者返回函数作为结果的函数,就叫高阶函数。
就这么简单。
Java里你写过Collections.sort(list, comparator)吧?这个sort方法接受一个Comparator对象作为参数------Comparator里面封装的是比较逻辑。这其实就是高阶函数的思想------只不过Java用对象来封装逻辑,Scala直接用函数。
java
// Java:用Comparator对象封装比较逻辑
Collections.sort(users, new Comparator<User>() {
@Override
public int compare(User u1, User u2) {
return u1.getAge() - u2.getAge();
}
});
scala
// Scala:直接传函数
users.sortBy(_.age)
users.sortWith(_.age < _.age)
这篇文章不讲"高阶函数是什么"这种入门废话,直接说干货:Scala的高阶函数底层是什么?为什么map/filter/reduce都是高阶函数?函数返回函数到底有什么用?

先从一个痛点说起
Java里你想对集合做操作,怎么做?------写for循环。
java
// Java:把每个元素翻倍
List<Integer> doubled = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
doubled.add(list.get(i) * 2);
}
// 过滤出大于1的元素
List<Integer> filtered = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
if (list.get(i) > 1) filtered.add(list.get(i));
}
// 求和
int sum = 0;
for (int i = 0; i < list.size(); i++) {
sum += list.get(i);
}
三个操作,三段for循环。你发现了吗?------这三段for循环的结构一模一样,只有中间那一步不一样。
翻倍的那一步是x * 2,过滤的那一步是x > 1,求和的那一步是x + y。
既然结构一样,为什么不把这个结构抽出来,只让调用方传"那一步不一样的逻辑"?
这就是高阶函数。
scala
// Scala:把结构抽出来,只传不一样的逻辑
list.map(_ * 2) // 翻倍------传一个"每个元素怎么变"的函数
list.filter(_ > 1) // 过滤------传一个"每个元素留不留"的函数
list.reduce(_ + _) // 求和------传一个"两个元素怎么合"的函数
看到了吗?map、filter、reduce------它们都是高阶函数。它们把for循环的结构封装了,只让你传"那一步不一样的逻辑"。
高阶函数的本质:把"怎么做"封装起来,让你只写"做什么"。
高阶函数的两种形式
高阶函数有两种形式------函数作为参数 ,和函数作为返回值。
形式1:函数作为参数
这是最常见的形式------map、filter、reduce都是这种。
scala
// 自定义一个高阶函数:接受一个函数作为参数
def applyTwice(f: Int => Int, x: Int): Int = {
f(f(x))
}
applyTwice(_ + 1, 5) // 7------先加1再加1
applyTwice(_ * 2, 5) // 20------先乘2再乘2
applyTwice接受两个参数------一个函数f,一个整数x。它把f应用两次。
这就是函数作为参数------你把"要做什么"作为函数传进去,高阶函数负责"怎么应用这个函数"。
形式2:函数作为返回值
这种形式更强大------函数返回另一个函数。
scala
// 函数返回函数:生成一个"加n"的函数
def addN(n: Int): Int => Int = {
(x: Int) => x + n
}
val addOne = addN(1)
val addTen = addN(10)
addOne(5) // 6
addTen(5) // 15
看到了吗?addN返回的是一个函数Int => Int。你传1进去,它返回一个"加1"的函数;你传10进去,它返回一个"加10"的函数。
这就是函数工厂------函数返回函数,让你可以批量生成函数。
和偏应用函数很像------但偏应用函数是"固定已有函数的参数",高阶函数返回函数是"生成全新的函数"。
集合库:高阶函数的主战场
Scala集合库里到处都是高阶函数。你每天写的map、flatMap、filter、reduce、fold、foreach------全是高阶函数。
scala
val list = List(1, 2, 3, 4, 5)
// map:每个元素怎么变
list.map(_ * 2) // List(2, 4, 6, 8, 10)
// filter:每个元素留不留
list.filter(_ % 2 == 0) // List(2, 4)
// reduce:两个元素怎么合
list.reduce(_ + _) // 15
// fold:带初始值的合并
list.fold(10)(_ + _) // 25------10 + 1 + 2 + 3 + 4 + 5
// flatMap:每个元素变成一个序列,再拍平
list.flatMap(x => List(x, x * 10))
// List(1, 10, 2, 20, 3, 30, 4, 40, 5, 50)
// foreach:对每个元素做副作用
list.foreach(println)
为什么Scala的集合操作这么强大?------因为高阶函数让你把"数据怎么变换"抽象成函数,然后传给集合。
Java 8的Stream也有这些------map、filter、reduce。但Java是被迫加的------因为函数式编程的思想太有价值了,不加不行。Scala从第一天就有------因为它本来就是函数式语言。
高阶函数的威力:函数组合
高阶函数最强大的地方不是单个用------而是组合。
scala
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 链式组合:先过滤,再变换,再求和
val result = numbers
.filter(_ % 2 == 0) // 偶数:2,4,6,8,10
.map(_ * 2) // 翻倍:4,8,12,16,20
.reduce(_ + _) // 求和:60
println(result) // 60
一行链式调用,做了三件事------过滤、变换、聚合。没有for循环,没有中间变量,没有样板代码。
你用Java写一下试试?------三段for循环,三个中间List,十几行代码。
这就是高阶函数组合的威力:把复杂的数据处理,拆成一串简单的函数变换。
每个高阶函数只做一件事------filter只管过滤,map只管变换,reduce只管聚合。你把它们组合起来,就完成了复杂的逻辑。
这就是函数式编程的核心思想:把复杂的计算,拆成简单函数的组合。
几个常见的坑
坑1:函数参数的类型签名
scala
// 函数参数的类型:Int => Int 表示一个Int到Int的函数
def apply(f: Int => Int, x: Int): Int = f(x)
// 两个参数的函数:(Int, Int) => Int
def combine(f: (Int, Int) => Int, a: Int, b: Int): Int = f(a, b)
很多人第一次写高阶函数的时候,搞不清函数参数的类型签名。记住:
A => B是一个参数的函数(A, B) => C是两个参数的函数() => A是没有参数的函数
坑2:by-name参数和高阶函数
Scala有一个特殊的语法------by-name参数:
scala
// 注意:这个不是函数参数,是by-name参数
def whileLoop(condition: => Boolean)(body: => Unit): Unit =
if (condition) { body; whileLoop(condition)(body) }
condition: => Boolean和condition: () => Boolean不一样------前者是by-name参数,调用的时候直接写condition;后者是函数参数,调用的时候要写condition()。
坑3:序列化问题
和所有函数式特性一样------高阶函数传的函数在底层也是FunctionN实例。在Flink/Spark里,要注意序列化问题。
什么时候用高阶函数?
判断标准很简单:
你是不是有一段逻辑,其中"结构"是固定的,但"具体做什么"是变化的?
如果是------把结构封装成高阶函数,把"具体做什么"作为函数参数传进去。
| 场景 | 用高阶函数吗? |
|---|---|
| 集合的map/filter/reduce | 用------典型场景 |
| 模板方法:固定流程,变化具体步骤 | 用 |
| 回调函数:事件触发后做什么 | 用 |
| 函数工厂:批量生成函数 | 用 |
| 逻辑完全固定,没有变化 | 不用 |
| 简单的一次性操作 | 不用 |
简单说:当你想把"怎么做"和"做什么"分开的时候,用高阶函数。
写在最后
我写这篇文章的时候,特意没罗列高阶函数有多少种用法------因为用法很简单,谁都能写。
真正值得花时间理解的是:高阶函数为什么是函数式编程的核心?
因为函数式编程的核心思想就是:把函数当一等公民,用函数来抽象和组合逻辑。
没有高阶函数,函数就不能当参数传------那你就没有map、filter、reduce。没有这些,你就只能写for循环------命令式编程。
有了高阶函数,你可以把逻辑封装成函数,传给其他函数------你可以组合函数、变换函数、生成函数。这就是函数式编程。
Java花了很长时间才意识到这一点------Java 8加了Lambda和Stream。但Java的高阶函数是不完整的------它是"对象封装函数",不是"函数本身"。
Scala从第一天就有完整的高阶函数------因为它是函数式语言,高阶函数就是它的核心。
高阶函数不是一个"高级特性"------它是函数式编程的基石。它让你从"写怎么做"变成"写做什么"------从命令式变成声明式。
你写list.map(_ * 2)的时候,你没有写循环怎么遍历、怎么累加、怎么返回------你只写了"每个元素翻倍"。这就是声明式------告诉计算机做什么,不告诉它怎么做。
这就是高阶函数的价值:把程序员从"怎么做"的细节里解放出来,专注在"做什么"。