jOOL简介

2025/03/25

1. 概述

在本文中,我们将研究jOOL库-jOOQ的另一款产品。

2. Maven依赖

让我们首先向pom.xml中添加Maven依赖:

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

你可以在此处找到最新版本。

3. 函数式接口

在Java 8中,函数式接口非常有限,它们最多接收两个参数,并且没有太多附加功能。

jOOL通过提供一组新的函数接口解决了这个问题,这些接口甚至可以接收16个参数(从Function1Function16),并通过其他方便的方法进行丰富。

例如,要创建一个接收3个参数的函数,我们可以使用Function3:

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

在纯Java中,你需要自己实现它。除此之外,来自jOOL的函数式接口有一个方法applyPartially()允许我们轻松地执行部分应用:

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);

当我们有一个Function2类型的方法时,我们可以使用toBiFunction()方法轻松地将它转换为标准的Java BiFunction:

BiFunction biFunc = addTwoNumbers.toBiFunction();

类似地,Function1类型中有一个toFunction()方法。

4. 元组

元组是函数式编程世界中非常重要的结构,它是一个类型化的值容器,其中每个值都可以有不同的类型。元组通常用作函数参数

在对事件流进行转换时,它们也非常有用。在jOOL中,我们拥有可以包装1到16个值的元组,由Tuple1到Tuple16类型提供:

tuple(2, 2)

对于4个值:

tuple(1,2,3,4);

让我们考虑一个例子,当我们有一个包含3个值的元组序列时:

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"))
);

我们可以对元组使用不同类型的转换。首先,我们调用limit2()方法只从Tuple3中获取两个值。然后,我们调用concat()方法来拼接两个元组。

结果,我们得到了Tuple4类型的值。

5. 序列

Seq构造在Stream上添加更高级别的方法,但经常使用其底层方法。

5.1 包含操作

我们可以找到几种检查Seq中是否存在元素的方法变体,其中一些方法使用Stream类中的anyMatch()方法:

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 拼接操作

当我们有两个流并且想要将它们拼接起来(类似于两个数据集的SQL连接操作)时,使用标准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());

assertEquals(collect, Arrays.asList(1, 2));

我们需要将right流收集到一个列表中,以防止java.lang.IllegalStateException: stream has already been operated or closed。接下来,我们需要通过从filter方法访问rightCollected列表来进行副作用操作。这是一种容易出错且不太优雅的拼接两个数据集的方法。

幸运的是,Seq有一些有用的方法可以对数据集进行内联、左连接和右连接。这些方法隐藏了它的实现,只暴露了优雅API。

我们可以使用innerJoin()方法进行内连接:

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))
);

我们可以相应地进行右连接和左连接:

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))
);

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))
);

甚至有一个crossJoin()方法可以对两个数据集进行笛卡尔连接:

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()方法从源序列中重复获取元素,它将创建一个无限流,因此在将结果收集到列表中时需要小心,因此我们需要使用limit()方法将无限序列转换为有限序列:

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

假设我们要将一个序列中的所有元素复制到第二个序列,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))
);

duplicate()方法的返回类型是两个序列的元组。

假设我们有一个整数序列,并且我们想使用某个谓词将该序列拆分为两个序列,我们可以使用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 API按键对元素进行分组既麻烦又不直观-因为我们需要将collect()方法与Collectors.groupingBy收集器一起使用。

Seq将该代码隐藏在返回Map的groupBy()方法后面,因此无需显式使用collect()方法:

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 跳过元素

假设我们有一个元素序列,并且我们想在谓词不匹配时跳过元素。当谓词满足时,元素应该落在结果序列中。

我们可以为此使用skipWhile()方法:

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

我们可以使用skipUntil()方法实现相同的结果:

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

5.6 压缩序列

当我们处理元素序列时,通常需要将它们压缩成一个序列。

zip () API可用于将两个序列压缩为一个:

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

结果序列包含两个元素的元组。

当我们压缩两个序列时,但我们想以特定方式压缩它们时,我们可以将BiFunction传递给定义压缩元素方式的zip()方法:

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")
);

有时,通过zipWithIndex() API使用该序列中元素的索引来压缩序列很有用:

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

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

假设我们有一个方法,它接收一个字符串并可以抛出一个受检的异常:

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

然后,我们想将该方法应用于Stream的每个元素。没有办法在更高级别处理该异常,因此我们需要在map()方法中处理该异常:

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());

assertEquals(
    collect,
    Arrays.asList(1, 1, 1)
);

由于Java中函数式接口的设计,我们对该异常无能为力,因此在catch子句中,我们将受检异常转换为非受检异常。

幸运的是,在jOOL中有一个Unchecked类,它具有可以将受检异常转换为非受检异常的方法

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

assertEquals(
    collect,
    Arrays.asList(1, 1, 1)
);

我们将对methodThatThrowsChecked()的调用包装到Unchecked.function()方法中,该方法负责处理底层异常的转换。

7. 总结

本文介绍如何使用jOOL库,该库将有用的附加方法添加到Java标准Stream API。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章