1. 概述

本文将介绍 jOOL 库——这是 jOOQ 团队的又一力作。作为 Java 开发者,我们经常需要处理函数式编程场景,而 jOOL 正是为此设计的增强工具库。

2. Maven 依赖

首先在 pom.xml 中添加依赖:

<dependency>
    <groupId>org.jooq</groupId>
    <artifactId>jool</artifactId>
    <version>0.9.12</version>
</dependency>

最新版本可在 Maven 中央仓库 查询。

3. 函数式接口

Java 8 的函数式接口限制较多:最多支持两个参数,且功能单一。jOOL 通过提供更强大的函数式接口解决了这些问题:

支持最多 16 个参数(从 Function1Function16
提供实用扩展方法

示例:三参数函数

Function3<String, String, String, Integer> lengthSum
  = (v1, v2, v3) -> v1.length() + v2.length() + v3.length();

部分应用(Partial Application)

Function2<Integer, Integer, Integer> addTwoNumbers = (v1, v2) -> v1 + v2;
Function1<Integer, Integer> addToTwo = addTwoNumbers.applyPartially(2);

Integer result = addToTwo.apply(5);
assertEquals(result, (Integer) 7); // 结果为 7

与 Java 标准接口转换

BiFunction biFunc = addTwoNumbers.toBiFunction(); // Function2 → BiFunction

⚠️ 注意:Function1 提供 toFunction() 方法转换为标准 Function

4. 元组(Tuples)

元组是函数式编程的核心概念,它是一个类型安全的容器,可存储不同类型的值。在 jOOL 中,元组通过 Tuple1Tuple16 支持 1-16 个值:

tuple(2, 2)              // Tuple2
tuple(1, 2, 3, 4);       // Tuple4

元组变换示例

Seq<Tuple3<String, String, Integer>> personDetails = Seq.of(
  tuple("michael", "similar", 49),
  tuple("jodie", "variable", 43));
Tuple2<String, String> tuple = tuple("winter", "summer");

List<Tuple4<String, String, String, String>> result = personDetails
  .map(t -> t.limit2().concat(tuple)).toList();

assertEquals(
  result,
  Arrays.asList(
    tuple("michael", "similar", "winter", "summer"), 
    tuple("jodie", "variable", "winter", "summer")
  )
);

操作解析

  1. limit2()Tuple3 截取前两个值
  2. concat() 连接两个元组
  3. 最终生成 Tuple4

5. Seq

Seq 是对 Java Stream 的增强封装,提供了更丰富的操作方法。

5.1. 包含操作

assertTrue(Seq.of(1, 2, 3, 4).contains(2));          // 包含单个元素
assertTrue(Seq.of(1, 2, 3, 4).containsAll(2, 3));    // 包含所有元素
assertTrue(Seq.of(1, 2, 3, 4).containsAny(2, 5));   // 包含任意元素

5.2. 连接操作

标准 Stream 实现连接操作非常繁琐:

Stream<Integer> left = Stream.of(1, 2, 4);
Stream<Integer> right = Stream.of(1, 2, 3);

List<Integer> rightCollected = right.collect(Collectors.toList());
List<Integer> collect = left
  .filter(rightCollected::contains)
  .collect(Collectors.toList());

踩坑点:必须先收集 right 流,否则会抛出 IllegalStateException

jOOL 提供了优雅的解决方案:

内连接(Inner Join)

assertEquals(
  Seq.of(1, 2, 4).innerJoin(Seq.of(1, 2, 3), (a, b) -> a == b).toList(),
  Arrays.asList(tuple(1, 1), tuple(2, 2))
);

左连接(Left Join)

assertEquals(
  Seq.of(1, 2, 4).leftOuterJoin(Seq.of(1, 2, 3), (a, b) -> a == b).toList(),
  Arrays.asList(tuple(1, 1), tuple(2, 2), tuple(4, null))
);

右连接(Right Join)

assertEquals(
  Seq.of(1, 2, 4).rightOuterJoin(Seq.of(1, 2, 3), (a, b) -> a == b).toList(),
  Arrays.asList(tuple(1, 1), tuple(2, 2), tuple(null, 3))
);

交叉连接(Cross Join)

assertEquals(
  Seq.of(1, 2).crossJoin(Seq.of("A", "B")).toList(),
  Arrays.asList(tuple(1, "A"), tuple(1, "B"), tuple(2, "A"), tuple(2, "B"))
);

5.3. 操纵 Seq

循环生成(Cycle)

assertEquals(
  Seq.of(1, 2, 3).cycle().limit(9).toList(),
  Arrays.asList(1, 2, 3, 1, 2, 3, 1, 2, 3)
);

⚠️ 注意:cycle() 生成无限流,必须配合 limit() 使用!

复制序列(Duplicate)

assertEquals(
  Seq.of(1, 2, 3).duplicate().map((first, second) -> tuple(first.toList(), second.toList())),
  tuple(Arrays.asList(1, 2, 3), Arrays.asList(1, 2, 3))
);

分区(Partition)

assertEquals(
  Seq.of(1, 2, 3, 4).partition(i -> i > 2)
    .map((first, second) -> tuple(first.toList(), second.toList())),
  tuple(Arrays.asList(3, 4), Arrays.asList(1, 2))
);

5.4. 分组元素

标准 Stream 的分组操作需要写冗长的 Collectors.groupingBy

Map<Integer, List<Integer>> expectedAfterGroupBy = new HashMap<>();
expectedAfterGroupBy.put(1, Arrays.asList(1, 3));
expectedAfterGroupBy.put(0, Arrays.asList(2, 4));

assertEquals(
  Seq.of(1, 2, 3, 4).groupBy(i -> i % 2),
  expectedAfterGroupBy
);

5.5. 跳过元素

条件跳过(Skip While)

assertEquals(
  Seq.of(1, 2, 3, 4, 5).skipWhile(i -> i < 3).toList(),
  Arrays.asList(3, 4, 5)
);

条件保留(Skip Until)

assertEquals(
  Seq.of(1, 2, 3, 4, 5).skipUntil(i -> i == 3).toList(),
  Arrays.asList(3, 4, 5)
);

5.6. 压缩序列

基础压缩(Zip)

assertEquals(
  Seq.of(1, 2, 3).zip(Seq.of("a", "b", "c")).toList(),
  Arrays.asList(tuple(1, "a"), tuple(2, "b"), tuple(3, "c"))
);

自定义压缩规则

assertEquals(
  Seq.of(1, 2, 3).zip(Seq.of("a", "b", "c"), (x, y) -> x + ":" + y).toList(),
  Arrays.asList("1:a", "2:b", "3:c")
);

带索引压缩

assertEquals(
  Seq.of("a", "b", "c").zipWithIndex().toList(),
  Arrays.asList(tuple("a", 0L), tuple("b", 1L), tuple("c", 2L))
);

6. 将受检异常转换为非受检异常

当在 Stream 中调用可能抛出受检异常的方法时,标准写法非常繁琐:

public Integer methodThatThrowsChecked(String arg) throws Exception {
    return arg.length();
}

List<Integer> collect = Stream.of("a", "b", "c").map(elem -> {
    try {
        return methodThatThrowsChecked(elem);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}).collect(Collectors.toList());

痛点:必须手动捕获异常并转换为运行时异常。

jOOL 提供了简单粗暴的解决方案:

List<Integer> collect = Stream.of("a", "b", "c")
  .map(Unchecked.function(elem -> methodThatThrowsChecked(elem)))
  .collect(Collectors.toList());

Unchecked.function() 自动处理异常转换,代码瞬间清爽!

7. 结论

jOOL 通过以下特性显著增强了 Java 的函数式编程能力:

  • 强大的函数式接口(支持最多 16 个参数)
  • 灵活的元组操作
  • 丰富的 Seq 方法(连接、分组、压缩等)
  • 优雅的异常处理机制

这些工具让复杂的数据处理逻辑变得简单直观。完整示例代码可在 GitHub 项目 中获取(Maven 项目,可直接导入运行)。


原始标题:Introduction to jOOL | Baeldung