Java 函数式编程

更新于 2025-12-25

Jakob Jenkov 2023-02-13

“Java 函数式编程”指的是在 Java 中使用函数式编程范式。历史上,Java 并不容易支持函数式编程,甚至有些函数式编程的特性在 Java 中根本无法实现。直到 Java 8,Oracle 才开始努力让函数式编程变得更简单,这一努力在一定程度上取得了成功。

在本篇 Java 函数式编程教程中,我将介绍函数式编程的基础知识,并说明其中哪些部分可以在 Java 中实现。


函数式编程基础

函数式编程包含以下核心概念:

  • 函数是一等公民(First-class objects)
  • 纯函数(Pure functions)
  • 高阶函数(Higher-order functions)

而纯函数式编程还遵循一组规则:

  • 无状态(No state)
  • 无副作用(No side effects)
  • 不可变变量(Immutable variables)
  • 优先使用递归而非循环(Favour recursion over looping)

这些概念和规则将在本教程后续部分逐一解释。

即使你不能始终严格遵守所有规则,依然可以从函数式编程的思想中获益。正如你将看到的那样,函数式编程并非适用于所有问题。特别是“无副作用”的理念,使得诸如写入数据库这类操作(属于副作用)变得困难。你需要学会判断哪些问题适合用函数式编程来解决,哪些不适合。


函数是一等公民

在函数式编程范式中,函数是语言中的一等公民。这意味着你可以像创建 String、Map 或其他对象的实例一样,创建一个“函数实例”,并用变量引用它。函数也可以作为参数传递给其他函数。

在 Java 中,方法并不是一等公民。最接近的实现是 Java Lambda 表达式。本教程不会详细讲解 Java Lambda 表达式,因为我已在 Java Lambda 表达式教程(含文字和视频)中做了详细介绍。


纯函数(Pure Functions)

如果一个函数满足以下两个条件,它就是一个纯函数

  1. 函数的执行没有副作用
  2. 函数的返回值仅依赖于传入的参数

下面是一个 Java 中纯函数(方法)的例子:

public class ObjectWithPureFunction {
    public int sum(int a, int b) {
        return a + b;
    }
}

注意,sum() 方法的返回值只依赖于输入参数,并且没有任何副作用(即不会修改函数外部的任何状态)。

相反,下面是一个非纯函数的例子:

public class ObjectWithNonPureFunction {
    private int value = 0;

    public int add(int nextValue) {
        this.value += nextValue;
        return this.value;
    }
}

注意,add() 方法使用了成员变量来计算返回值,并且修改了 value 成员变量的状态,因此具有副作用。


高阶函数(Higher Order Functions)

如果一个函数满足以下任一条件,它就是高阶函数

  1. 函数接受一个或多个函数作为参数;
  2. 函数返回另一个函数作为结果。

在 Java 中,我们能实现的最接近高阶函数的形式是:方法接受一个或多个 Lambda 表达式作为参数,并返回另一个 Lambda 表达式

下面是一个 Java 中高阶函数的例子:

public class HigherOrderFunctionClass {
    public <T> IFactory<T> createFactory(IProducer<T> producer, IConfigurator<T> configurator) {
        return () -> {
            T instance = producer.produce();
            configurator.configure(instance);
            return instance;
        };
    }
}

注意,createFactory() 方法返回了一个 Lambda 表达式,这满足高阶函数的第一个条件。

此外,该方法接收的两个参数 producerconfigurator 都是接口的实现(IProducerIConfigurator)。由于 Java 的 Lambda 表达式必须实现一个函数式接口(functional interface),因此只要这些接口符合要求,就可以用 Lambda 表达式来实现。

假设这些接口如下所示:

public interface IFactory<T> {
    T create();
}

public interface IProducer<T> {
    T produce();
}

public interface IConfigurator<T> {
    void configure(T t);
}

可以看到,这三个接口都是函数式接口(只有一个抽象方法),因此可以被 Lambda 表达式实现。所以,createFactory() 是一个高阶函数。


无状态(No State)

如前所述,函数式编程范式的一个规则是无状态。这里的“无状态”通常指函数不依赖外部状态。函数内部可以有局部变量用于临时存储状态,但不能引用所属类或对象的成员变量。

下面是一个不使用外部状态的函数示例:

public class Calculator {
    public int sum(int a, int b) {
        return a + b;
    }
}

而下面这个函数则使用了外部状态,违反了“无状态”规则:

public class Calculator {
    private int initVal = 5;

    public int sum(int a) {
        return initVal + a;
    }
}

无副作用(No Side Effects)

函数式编程的另一条规则是无副作用,即函数不能改变其外部的任何状态。这种对外部状态的修改被称为副作用

“外部状态”既包括函数所属类或对象的成员变量,也包括函数参数内部的成员变量,以及文件系统、数据库等外部系统的状态。


不可变变量(Immutable Variables)

函数式编程的第三条规则是使用不可变变量。不可变变量有助于避免副作用,使程序更易于推理和测试。


优先使用递归而非循环(Favour Recursion Over Looping)

函数式编程的第四条规则是优先使用递归而不是传统的循环结构。递归通过函数调用来实现重复逻辑,使代码更具函数式风格。

不过,在 Java 中,另一种替代循环的方式是使用 Java Stream API,该 API 本身也受到函数式编程的启发。


函数式接口(Functional Interfaces)

Java 中的函数式接口是指只包含一个抽象方法的接口。所谓“抽象方法”,指的是没有实现的方法。一个接口可以包含多个带有实现的方法(如 default 方法或 static 方法),但只要只有一个未实现的方法,它就被视为函数式接口。

例如,以下是一个函数式接口:

public interface MyInterface {
    public void run();
}

下面这个接口虽然包含 default 和 static 方法,但仍然是函数式接口:

public interface MyInterface2 {
    public void run();

    public default void doIt() {
        System.out.println("doing it");
    }

    public static void doItStatically() {
        System.out.println("doing it statically");
    }
}

注意,只有 run() 是抽象方法,其余都有实现。因此它仍可被 Lambda 表达式实现。

但如果接口中有多个未实现的方法,它就不再是函数式接口,也就不能用 Lambda 表达式来实现。