闭包柯里化与高阶函数

闭包、柯里化与高阶函数

1. 闭包

闭包是“函数及其捕获的词法环境”。即使定义函数的方法已经执行结束,返回的函数仍能访问当时捕获的变量。

代码块JAVA · 6 行收起展开
static IntUnaryOperator add(int base) {
    return value -> base + value;
}

IntUnaryOperator add10 = add(10);
int result = add10.applyAsInt(5); // 15

这里返回的 Lambda 捕获了 base,所以 add(10) 结束后仍能使用它。

bf544fca1d7a45b28b3d68aa2fd0079e

2. effectively final 规则

Java Lambda 捕获的局部变量必须是 finaleffectively final:初始化后没有再次赋值。

代码块JAVA · 3 行收起展开
int offset = 10;
Function<Integer, Integer> addOffset = x -> x + offset;
// offset++; // 若重新赋值,上面的 Lambda 将无法编译

原因不是“变量永远不可变”,而是 Java 捕获局部变量的值。禁止重新赋值可以避免栈帧结束后变量生命周期和多线程可见性产生歧义。

对于引用变量,不能让引用指向另一个对象,但对象内部仍可能被修改:

代码块JAVA · 2 行收起展开
List<String> names = new ArrayList<>();
Runnable task = () -> names.add("A"); // 能编译,但存在副作用

“可以编译”不代表“适合函数式编程”。捕获可变集合仍可能引发并发和可读性问题。

26b8b902f9b34c66bc3491f836f166b9

3. 柯里化与偏函数应用

柯里化把接收多个参数的函数转换为一连串单参数函数:

代码块JAVA · 5 行收起展开
Function<Integer, Function<Integer, Integer>> add =
        a -> b -> a + b;

Function<Integer, Integer> add10 = add.apply(10);
int result = add10.apply(5); // 15

add.apply(10) 固定了第一个参数,返回的新函数只等待第二个参数。工程中更常见的说法是偏函数应用:预先绑定部分参数,得到更具体的函数。

代码块JAVA · 5 行收起展开
static Predicate<Integer> atLeast(int minimum) {
    return value -> value >= minimum;
}

numbers.stream().filter(atLeast(60)).toList();

90249a5ef0f3468e8381a82aa01698e6

1e82ccf89a2e452c924aa8958d0cdef4

4. 什么时候有价值

  • 创建可复用的校验规则,如 olderThan(18)
  • 提前绑定配置或依赖,生成专用函数。
  • 将复杂判断拆成可组合的 Predicate
  • 在测试中注入行为,而不是把分支写死。

不要为了形式强行柯里化。Java 对多层 Function<A, Function<B, ...>> 的类型表达较啰嗦,参数不需要分阶段绑定时,普通方法或 BiFunction 往往更清晰。

延伸阅读