函数式编程模式:高阶函数、函数组合、柯里化、惰性求值,以及Java Stream API
说实话,函数式编程这个概念,我早年做C++的时候觉得挺玄乎的。那时候满脑子都是对象、继承、虚函数,觉得函数式那套东西是学术圈玩的。直到后来接手一个大数据处理项目,代码里全是嵌套循环和可变状态,调试起来简直要命。嗯,从那以后我才真正开始重视函数式编程。
这一章咱们聊聊函数式编程模式。说白了,就是把函数当成一等公民来用。你可以把函数传来传去,组合起来,甚至延迟执行。Java 8引入的Stream API,就是函数式思想在Java世界的一次漂亮落地。
高阶函数:函数也能当参数
高阶函数,就是那些接收函数作为参数,或者返回一个函数的函数。我在项目中见过太多人写重复的模板代码,其实用高阶函数就能优雅解决。
举个实际例子。假设你要对集合里的每个元素做某种操作,传统写法你得写for循环。用高阶函数,你只需要传一个lambda进去。
// Java示例:Stream的map就是典型的高阶函数
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> nameLengths = names.stream()
.map(name -> name.length())
.collect(Collectors.toList());
// 结果:[5, 3, 7]
// C++示例:用std::function实现高阶函数
#include <functional>
#include <vector>
#include <algorithm>
std::vector<int> applyToAll(std::vector<int> nums, std::function<int(int)> func) {
std::vector<int> result;
std::transform(nums.begin(), nums.end(), std::back_inserter(result), func);
return result;
}
// 调用
auto doubled = applyToAll({1, 2, 3}, [](int x) { return x * 2; });
函数组合:把简单函数拼成复杂逻辑
函数组合,就是把多个小函数串起来,形成一条处理流水线。每个函数只做一件事,组合起来却能完成复杂任务。
我记得有一次做报表系统,需要把原始数据经过过滤、转换、排序、汇总四个步骤。如果用传统写法,中间变量满天飞。用函数组合,代码清晰得像流水线。
// Java:用Function接口实现组合
import java.util.function.Function;
Function<String, String> trim = String::trim;
Function<String, String> toUpper = String::toUpperCase;
Function<String, String> addPrefix = s -> "【" + s + "】";
// 组合:先trim,再转大写,最后加括号
Function<String, String> process = trim.andThen(toUpper).andThen(addPrefix);
String result = process.apply(" hello world ");
// 结果:"【HELLO WORLD】"
// C++:用管道操作符模拟组合(需要C++20或自己实现)
// 这里展示一个简单的组合思路
auto compose = [](auto f, auto g) {
return [=](auto x) { return f(g(x)); };
};
auto trim = [](std::string s) { /* 实现trim逻辑 */ };
auto toUpper = [](std::string s) { /* 转大写 */ };
auto process = compose(toUpper, trim);
柯里化:把多参数函数拆成单参数链
柯里化这个词听起来挺唬人,其实意思很简单:把一个接收多个参数的函数,拆成一串只接收一个参数的函数。每个函数返回下一个函数,直到所有参数凑齐才执行真正的逻辑。
为什么要这么做?因为你可以"固定"部分参数,生成一个更具体的函数。这在配置化场景下特别有用。
// Java:手动实现柯里化
import java.util.function.Function;
// 一个普通的三参数函数
// (a, b, c) -> a + b + c
// 柯里化版本
Function<Integer, Function<Integer, Function<Integer, Integer>>> curriedSum =
a -> b -> c -> a + b + c;
// 使用:逐步传入参数
Function<Integer, Function<Integer, Integer>> add5 = curriedSum.apply(5);
Function<Integer, Integer> add5And3 = add5.apply(3);
int result = add5And3.apply(2); // 5 + 3 + 2 = 10
// C++:用lambda实现柯里化
auto curriedSum = [](int a) {
return [a](int b) {
return [a, b](int c) {
return a + b + c;
};
};
};
auto add5 = curriedSum(5);
auto add5And3 = add5(3);
int result = add5And3(2); // 10
惰性求值:用到的时候才算
惰性求值,就是表达式不立即计算,而是等到真正需要结果时才去算。这能避免不必要的计算,还能处理无限数据结构。
Java的Stream就是惰性的。你调用filter、map这些中间操作时,数据并没有真正处理。只有当你调用collect、forEach这些终端操作时,计算才真正发生。
// Java Stream的惰性求值
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> stream = numbers.stream()
.filter(n -> {
System.out.println("过滤: " + n);
return n % 2 == 0;
})
.map(n -> {
System.out.println("映射: " + n);
return n * n;
});
// 此时没有任何输出!因为还没触发终端操作
System.out.println("Stream已创建,但还没计算");
// 只有调用collect时,才真正执行
List<Integer> result = stream.collect(Collectors.toList());
// 输出:
// 过滤: 1
// 过滤: 2
// 映射: 2
// 过滤: 3
// ...
// C++:用range-v3库实现惰性求值(C++20)
#include <range/v3/all.hpp>
#include <iostream>
auto numbers = ranges::views::ints(1, 11);
auto even_squares = numbers
| ranges::views::filter([](int n) {
std::cout << "过滤: " << n << "\n";
return n % 2 == 0;
})
| ranges::views::transform([](int n) {
std::cout << "映射: " << n << "\n";
return n * n;
});
// 此时没有输出,惰性的
std::cout << "视图已创建,但还没计算\n";
// 遍历时才真正计算
for (int v : even_squares) {
std::cout << "结果: " << v << "\n";
}
Java Stream API:函数式编程的集大成者
Java 8的Stream API,把上面这些概念都揉在了一起。它提供了高阶函数(map、filter、reduce)、惰性求值(中间操作延迟执行)、函数组合(链式调用)。
我个人觉得,Stream API最厉害的地方在于它把"数据流"这个抽象做得特别自然。你不需要关心数据是怎么遍历的,只需要声明你要做什么转换。
// Stream API综合示例
List<Transaction> transactions = getTransactions();
double result = transactions.stream()
.filter(t -> t.getType() == TransactionType.GROCERY)
.map(Transaction::getAmount)
.filter(amount -> amount > 100)
.reduce(0.0, Double::sum);
System.out.println("杂货类交易中超过100元的总额: " + result);
你看,这段代码没有for循环,没有临时变量,没有if-else嵌套。每一行都在说"我要做什么",而不是"我要怎么做"。这就是函数式编程的魅力。
知识体系总览
下面这张图,把函数式编程模式的核心概念串起来了。你可以看到它们之间的关系:高阶函数是基础,函数组合和柯里化是两种重要的组合方式,惰性求值是性能优化手段,而Stream API是这些概念在Java中的具体实现。
这张图把函数式编程模式的知识结构理清楚了。从上到下看:四个核心概念支撑起整个模式,它们汇聚到具体的API实现中,最终落地为一组核心特性。你写代码时,其实就是在用这些特性。
公众号:蓝海资料掘金营,微信deep3321