Introduction to jOOL – JOOL简介

最后修改: 2017年 2月 25日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this article, we will be looking at the jOOL library – another product from jOOQ.

在这篇文章中,我们将关注jOOL库–来自jOOQ的另一个产品。

2. Maven Dependency

2.Maven的依赖性

Let’s start by adding a Maven dependency to your pom.xml:

我们先在pom.xml中添加一个Maven依赖项。

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

You can find the latest version here.

你可以在这里找到最新版本

3. Functional Interfaces

3.功能接口

In Java 8, functional interfaces are quite limited. They accept the maximum number of two parameters and do not have many additional features.

在Java 8中,功能接口是相当有限的。它们最多接受两个参数,并且没有很多额外的功能。

jOOL fixes that by proving a set of new functional interfaces that can accept even 16 parameters (from Function1 up to Function16) and are enriched with additional handy methods.

jOOL通过证明一组新的函数接口来解决这个问题,这些接口甚至可以接受16个参数(从Function1Function16)。,并以额外的方便的方法来丰富。

For example, to create a function that takes three arguments, we can use Function3:

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

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

In pure Java, you would need to implement it by yourself. Besides that, functional interfaces from jOOL have a method applyPartially() that allows us to perform a partial application easily:

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

When we have a method that is of a Function2 type, we can transform it easily to a standard Java BiFunction by using a toBiFunction() method:

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

BiFunction biFunc = addTwoNumbers.toBiFunction();

Similarly, there is a toFunction() method in Function1 type.

同样,在Function1类型中也有一个toFunction()方法。

4. Tuples

4.图元

A tuple is a very important construct in a functional programming world. It’s a typed container for values where each value can have a different type. Tuples are often used as function arguments.

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

They’re also very useful when doing transformations on a stream of events. In jOOL, we have tuples that can wrap from one up to sixteen values, provided by Tuple1 up to Tuple16 types:

在对事件流进行转换时,它们也非常有用。在jOOL中,我们的图元可以包裹从一个到16个值,由Tuple1Tuple16类型提供。

tuple(2, 2)

And for four values:

而对于四个价值。

tuple(1,2,3,4);

Let’s consider an example when we have a sequence of tuples that carried 3 values:

让我们考虑一个例子,当我们有一个携带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"))
);

We can use different kinds of transformations on tuples. First, we call a limit2() method to take only two values from Tuple3. Then, we are calling a concat() method to concatenate two tuples.

我们可以对图元使用不同种类的转换。首先,我们调用一个limit2() 方法,从图元3中只取两个值。然后,我们在调用一个concat()方法来串联两个图元。

In the result, we get values that are of a Tuple4 type.

在结果中,我们得到的值是Tuple4类型。

5. Seq

5.Seq

The Seq construct adds higher-level methods on a Stream while often uses its methods underneath.

Seq结构在Stream上增加了更高层次的方法,同时经常使用其下面的方法。

5.1. Contains Operations

5.1.包含操作

We can find a couple variants of methods checking for a presence of elements in a Seq. Some of those methods use an anyMatch() method from a Stream class:

我们可以找到一些检查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. Join Operations

5.2.加入操作

When we have two streams and we want to join them (similar to a SQL join operation of two datasets), using a standard Stream class is not a very elegant way to do this:

当我们有两个流并且我们想要连接它们(类似于两个数据集的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));

We need to collect right stream to a list, to prevent java.lang.IllegalStateException: stream has already been operated upon or closed. Next, we need to make a side effect operation by accessing a rightCollected list from a filter method. It is error prone and not elegant way to join two data sets.

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

Fortunately, Seq has useful methods to do inner, left and right joins on data sets. Those methods hide an implementation of it exposing elegant API.

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

We can do an inner join by using an innerJoin() method:

我们可以通过使用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))
);

We can do right and left joins accordingly:

我们可以做相应的右键和左键。

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

There is even a crossJoin() method that makes possible to make a cartesian join of two datasets:

甚至还有一个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. Manipulating a Seq

5.3.操纵一个序列

Seq has many useful methods for manipulating sequences of elements. Let’s look at some of them.

Seq有许多有用的方法来操作元素的序列。让我们看一下其中的一些。

We can use a cycle() method to take repeatedly elements from a source sequence. It will create an infinite stream, so we need to be careful when collecting results to a list thus we need to use a limit() method to transform infinite sequence into finite one:

我们可以使用cycle() 方法从一个源序列中重复获取元素。这将会产生一个无限的流,所以我们在收集结果到一个列表时需要小心,因此我们需要使用limit() 方法来将无限的序列转化为有限的序列。

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

Let’s say that we want to duplicate all elements from one sequence to the second sequence. The duplicate() method does exactly that:

比方说,我们想把一个序列中的所有元素复制到第二个序列中。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))
);

Returning type of a duplicate() method is a tuple of two sequences.

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

Let’s say that we have a sequence of integers and we want to split that sequence into two sequences using some predicate. We can use a partition() method:

假设我们有一个整数序列,我们想用一些谓词把这个序列分成两个序列。我们可以使用一个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. Grouping Elements

5.4.分组元素

Grouping elements by a key using the Stream API is cumbersome and non-intuitive – because we need to use collect() method with a Collectors.groupingBy collector.

使用Stream API按键对元素进行分组是很麻烦的,也是不直观的–因为我们需要使用collect() 方法和Collectors.groupingBycollector。

Seq hides that code behind a groupBy() method that returns Map so there is no need to use a collect() method explicitly:

Seq将该代码隐藏在返回MapgroupBy()方法后面,因此不需要明确使用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. Skipping Elements

5.5.跳过元素

Let’s say that we have a sequence of elements and we want to skip elements while a predicate is not matched. When a predicate is satisfied, elements should land in a resulting sequence.

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

We can use a skipWhile() method for that:

我们可以使用一个skipWhile()方法来实现。

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

We can achieve the same result using a skipUntil() method:

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

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

5.6. Zipping Sequences

5.6.压缩序列

When we’re processing sequences of elements, often there is a need to zip them into one sequence.

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

The zip() API that could be used to zip two sequences into one:

可用于将两个序列压缩成一个的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"))
);

The resulting sequence contains tuples of two elements.

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

When we are zipping two sequences, but we want to zip them in a specific way we can pass a BiFunction to a zip() method that defines the way of zipping elements:

当我们对两个序列进行压缩时,但我们想以特定的方式压缩它们,我们可以将一个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")
);

Sometimes, it is useful to zip sequence with an index of elements in this sequence, via the zipWithIndex() API:

有时,通过zipWithIndex() API,用这个序列中元素的索引来压缩序列是很有用的。

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

6. Converting Checked Exceptions to Unchecked

6.将检查过的异常转换为未检查过的异常

Let’s say that we have a method that takes a string and can throw a checked exception:

假设我们有一个方法,它接收一个字符串,并能抛出一个检查过的异常。

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

Then we want to map elements of a Stream applying that method to each element. There is no way to handle that exception higher so we need to handle that exception in a map() method:

然后我们想要映射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)
);

There is not much we can do with that exception because of the design of functional interfaces in Java so in a catch clause, we are converting a checked exception into unchecked one.

由于Java中功能接口的设计,我们对这个异常没有什么办法,所以在catch子句中,我们要把一个有检查的异常转化为无检查的。

Fortunately, in a jOOL there is an Unchecked class that has methods that can convert checked exceptions into unchecked exceptions:

幸运的是,在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)
);

We are wrapping a call to a methodThatThrowsChecked() into an Unchecked.function() method that handles converting of exceptions underneath.

我们把对methodThatThrowsChecked()的调用包装成Unchecked.function()方法,在下面处理异常的转换。

7. Conclusion

7.结论

This article shows how to use the jOOL library that adds useful additional methods to the Java standard Stream API.

本文展示了如何使用jOOL库,该库为Java标准Stream API增加了有用的附加方法。

The implementation of all these examples and code snippets can be found in the GitHub project – this is a Maven project, so it should be easy to import and run as it is.

所有这些例子和代码片段的实现都可以在GitHub项目中找到–这是一个Maven项目,所以应该很容易导入并按原样运行。