1. 概述

本篇文章将带你快速了解 Functional Java 这个库,并通过几个实际例子展示它的基本用法。

2. Functional Java 库简介

Functional Java 是一个开源库,旨在帮助 Java 开发者更方便地进行函数式编程。它提供了一系列基础和高级的编程抽象,这些抽象在函数式编程中非常常见。

这个库的核心围绕着 F 接口展开。✅ 这个接口表示一个函数:接收类型为 A 的输入,返回类型为 B 的输出。 所有功能都构建在 Java 自身的类型系统之上。

3. Maven 依赖配置

首先,我们需要在 pom.xml 文件中添加所需的依赖:

<dependency>
    <groupId>org.functionaljava</groupId>
    <artifactId>functionaljava</artifactId>
    <version>4.8.1</version>
</dependency>
<dependency>
    <groupId>org.functionaljava</groupId>
    <artifactId>functionaljava-java8</artifactId>
    <version>4.8.1</version>
</dependency>
<dependency>
    <groupId>org.functionaljava</groupId>
    <artifactId>functionaljava-quickcheck</artifactId>
    <version>4.8.1</version>
</dependency>
<dependency>
    <groupId>org.functionaljava</groupId>
    <artifactId>functionaljava-java-core</artifactId>
    <version>4.8.1</version>
</dependency>

4. 定义函数

我们先来定义一个简单的函数,供后续示例使用。

不使用 Functional Java 时,一个乘二的方法可能是这样的:

public static final Integer timesTwoRegular(Integer i) {
    return i * 2;
}

而使用 Functional Java 后,我们可以更优雅地定义:

public static final F<Integer, Integer> timesTwo = i -> i * 2;

✅ 上面这段代码定义了一个 F 接口的实例,它接受一个 Integer 类型的输入,并返回该值的两倍。

再来看一个类似的函数,这次判断一个整数是否为偶数:

public static final F<Integer, Boolean> isEven = i -> i % 2 == 0;

5. 应用函数

有了函数之后,我们可以将其应用于数据集合。

Functional Java 提供了常见的数据类型,如 List、Set、Array 和 Map。⚠️ 需要注意的是,这些数据类型都是不可变的(immutable)。

此外,库中还提供了便捷方法,用于与 Java 标准集合类之间的转换。

下面是一个例子,我们将一个整数列表中的每个元素都乘以 2,并通过 map 方法实现:

public void multiplyNumbers_givenIntList_returnTrue() {
    List<Integer> fList = List.list(1, 2, 3, 4);
    List<Integer> fList1 = fList.map(timesTwo);
    List<Integer> fList2 = fList.map(i -> i * 2);

    assertTrue(fList1.equals(fList2));
}

map 会返回一个同样大小的列表,其中每个元素是原列表元素经过函数处理后的结果,原始列表本身不会改变。

下面是使用 isEven 函数的例子:

public void calculateEvenNumbers_givenIntList_returnTrue() {
    List<Integer> fList = List.list(3, 4, 5, 6);
    List<Boolean> evenList = fList.map(isEven);
    List<Boolean> evenListTrueResult = List.list(false, true, false, true);

    assertTrue(evenList.equals(evenListTrueResult));
}

✅ 由于 map 返回的是一个新的列表,我们可以继续对其结果再次调用 map。⚠️ 调用顺序会影响最终结果:

public void applyMultipleFunctions_givenIntList_returnFalse() {
    List<Integer> fList = List.list(1, 2, 3, 4);
    List<Integer> fList1 = fList.map(timesTwo).map(plusOne);
    List<Integer> fList2 = fList.map(plusOne).map(timesTwo);

    assertFalse(fList1.equals(fList2));
}

输出结果分别为:

List(3,5,7,9)
List(4,6,8,10)

6. 使用函数进行过滤

在函数式编程中,另一个常见操作是根据某些条件过滤数据。这些条件通常也以函数的形式提供,且必须返回布尔值。

下面的例子中,我们使用 isEven 函数过滤掉奇数:

public void filterList_givenIntList_returnResult() {
    Array<Integer> array = Array.array(3, 4, 5, 6);
    Array<Integer> filteredArray = array.filter(isEven);
    Array<Integer> result = Array.array(4, 6);

    assertTrue(filteredArray.equals(result));
}

⚠️ 这次我们使用的是 Array 而不是 List,但函数依然适用。✅ 这体现了函数抽象的优势:函数无需关心数据结构的具体类型。

Functional Java 的 Integer 类也提供了类似的标准函数,比如用于数值比较的函数。

7. 使用函数进行布尔逻辑判断

函数式编程中经常需要判断“是否所有元素满足某个条件”或“是否存在至少一个元素满足某个条件”。

Functional Java 提供了 existsforall 两个方法来简化这类逻辑:

public void checkForLowerCase_givenStringArray_returnResult() {
    Array<String> array = Array.array("Welcome", "To", "baeldung");
    assertTrue(array.exists(s -> List.fromString(s).forall(Characters.isLowerCase)));

    Array<String> array2 = Array.array("Welcome", "To", "Baeldung");
    assertFalse(array2.exists(s -> List.fromString(s).forall(Characters.isLowerCase)));

    assertFalse(array.forall(s -> List.fromString(s).forall(Characters.isLowerCase)));
}

在这个例子中,我们将字符串数组中的每个字符串转为字符列表,并对每个字符列表应用 forall(Characters.isLowerCase)

Characters.isLowerCase 是一个判断字符是否为小写的函数。因此,只有当整个字符串都为小写时,forall 才会返回 true

前两个测试使用了 exists 来判断是否存在至少一个小写字符串,第三个测试使用 forall 来验证是否所有字符串都为小写。

8. 使用函数处理可选值

在 Java 中处理可选值通常需要 null 检查或 isNotBlank 判断。Java 8 引入了 Optional 类型来优雅地处理这种情况。Functional Java 也提供了类似的机制:Option 类。

public void checkOptions_givenOptions_returnResult() {
    Option<Integer> n1 = Option.some(1);
    Option<Integer> n2 = Option.some(2);
    Option<Integer> n3 = Option.none();

    F<Integer, Option<Integer>> function = i -> i % 2 == 0 ? Option.some(i + 100) : Option.none();

    Option<Integer> result1 = n1.bind(function);
    Option<Integer> result2 = n2.bind(function);
    Option<Integer> result3 = n3.bind(function);

    assertEquals(Option.none(), result1);
    assertEquals(Option.some(102), result2);
    assertEquals(Option.none(), result3);
}

bind 方法用于将函数应用于 Option 值,如果值存在则执行函数,否则返回 none

9. 使用函数归约集合

“归约”操作(Reduce)指的是将集合中的多个元素合并为一个值。

Functional Java 将这一操作称为 折叠(fold)

需要指定一个函数来定义如何“折叠”元素。比如使用 Integers.add 函数来对整数列表求和。

根据折叠方向的不同,结果可能会不同。Functional Java 提供了 foldLeftfoldRight 两种方式:

public void foldLeft_givenArray_returnResult() {
    Array<Integer> intArray = Array.array(17, 44, 67, 2, 22, 80, 1, 27);

    int sumAll = intArray.foldLeft(Integers.add, 0);
    assertEquals(260, sumAll);

    int sumEven = intArray.filter(isEven).foldLeft(Integers.add, 0);
    assertEquals(148, sumEven);
}

第一个 foldLeft 对所有元素求和;第二个先过滤出偶数再求和。

10. 小结

本文只是对 Functional Java 库的一个简单介绍。✅ 如果你对函数式编程感兴趣,这个库值得深入研究。

源码可从 GitHub 获取。


原始标题:Introduction to Functional Java | Baeldung