Introduction to Protonpack – 质子包简介

最后修改: 2018年 9月 27日

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

1. Overview

1.概述

In this tutorial, we’ll look at the major features of Protonpack which is a library that expands the standard Stream API by adding some complimentary functionality.

在本教程中,我们将了解Protonpack的主要功能,它是一个库,通过添加一些补充功能来扩展标准Stream API

Refer to this write-up here to discover the fundamentals of the Java Stream API.

请参考这里的文章,了解Java Stream API的基本原理。

2. Maven Dependency

2.Maven的依赖性

To use the Protonpack library, we need to add a dependency in our pom.xml file:

为了使用Protonpack库,我们需要在我们的pom.xml文件中添加一个依赖项。

<dependency>
    <groupId>com.codepoetics</groupId>
    <artifactId>protonpack</artifactId>
    <version>1.15</version>
</dependency>

Check for the latest version on Maven Central.

Maven Central上查看最新版本。

3. StreamUtils

3.StreamUtils

This is the main class that expands Java’s standard Stream API.

这是扩展Java的标准Stream API的主类。

All the methods discussed here are intermediate operations, which means they modify a Stream but doesn’t trigger its processing.

这里讨论的所有方法都是中间操作,这意味着它们修改了Stream,但并不触发其处理。

3.1. takeWhile() and takeUntil()

3.1.takeWhile()takeUntil()

takeWhile() takes values from the source stream as long as they meet the supplied condition:

takeWhile()从源流中获取数值,只要它们满足所提供的条件

Stream<Integer> streamOfInt = Stream
  .iterate(1, i -> i + 1);
List<Integer> result = StreamUtils
  .takeWhile(streamOfInt, i -> i < 5)
  .collect(Collectors.toList());
assertThat(result).contains(1, 2, 3, 4);

Conversely, takeUntil() takes values until a value meets supplied condition and then stops:

相反,takeUntil()取值直到一个值满足所提供的条件,然后停止。

Stream<Integer> streamOfInt = Stream
  .iterate(1, i -> i + 1);
List<Integer> result = StreamUtils
  .takeUntil(streamOfInt, i -> i >= 5)
  .collect(Collectors.toList());
assertThat(result).containsExactly(1, 2, 3, 4);

In Java 9 onward, takeWhile() is part of the standard Stream API.

在Java 9以后,takeWhile()是标准Stream API的一部分。

3.2. zip()

3.2. zip()

zip() takes two or three streams as an input and a combiner function. The method takes a value from the same position of each stream and passes it to the combiner.

zip()将两个或三个流作为输入和一个组合器函数。该方法从每个流的相同位置获取一个值并将其传递给组合器

It does so until one of the streams runs out of values:

它这样做,直到其中一个流的值用完为止。

String[] clubs = {"Juventus", "Barcelona", "Liverpool", "PSG"};
String[] players = {"Ronaldo", "Messi", "Salah"};
Set<String> zippedFrom2Sources = StreamUtils
  .zip(stream(clubs), stream(players), (club, player) -> club + " " + player)
  .collect(Collectors.toSet());
 
assertThat(zippedFrom2Sources)
  .contains("Juventus Ronaldo", "Barcelona Messi", "Liverpool Salah");

Similarly, an overloaded zip() that takes three sources stream:

同样,一个重载的zip(),需要三个源流。

String[] leagues = { "Serie A", "La Liga", "Premier League" };
Set<String> zippedFrom3Sources = StreamUtils
  .zip(stream(clubs), stream(players), stream(leagues), 
    (club, player, league) -> club + " " + player + " " + league)
  .collect(Collectors.toSet());
 
assertThat(zippedFrom3Sources).contains(
  "Juventus Ronaldo Serie A", 
  "Barcelona Messi La Liga", 
  "Liverpool Salah Premier League");

3.3. zipWithIndex()

3.3.zipWithIndex()

zipWithIndex() takes values and zip each value with its index to create a stream of indexed values:

zipWithIndex()获取数值并将每个数值与其索引一起压缩,以创建一个有索引的数值流:

Stream<String> streamOfClubs = Stream
  .of("Juventus", "Barcelona", "Liverpool");
Set<Indexed<String>> zipsWithIndex = StreamUtils
  .zipWithIndex(streamOfClubs)
  .collect(Collectors.toSet());
assertThat(zipsWithIndex)
  .contains(Indexed.index(0, "Juventus"), Indexed.index(1, "Barcelona"), 
    Indexed.index(2, "Liverpool"));

3.4. merge()

3.4.merge()

merge() works with multiple source streams and a combiner. It takes the value of the same index position from each source stream and passes it to the combiner.

merge()在多个源流和一个组合器中工作。它从每个源流中获取相同索引位置的值并将其传递给组合器

The method works by taking 1 value from the same index from each stream in succession, starting from the seed value.

该方法的工作原理是,从种子值开始,连续从每个流的相同索引中抽取1个值。

Then the value is passed to the combiner, and the resulting combined value is fed back to the combiner to create the next value:

然后,该值被传递给组合器,产生的组合值被反馈给组合器以创建下一个值。

Stream<String> streamOfClubs = Stream
  .of("Juventus", "Barcelona", "Liverpool", "PSG");
Stream<String> streamOfPlayers = Stream
  .of("Ronaldo", "Messi", "Salah");
Stream<String> streamOfLeagues = Stream
  .of("Serie A", "La Liga", "Premier League");

Set<String> merged = StreamUtils.merge(
  () ->  "",
  (valOne, valTwo) -> valOne + " " + valTwo,
  streamOfClubs,
  streamOfPlayers,
  streamOfLeagues)
  .collect(Collectors.toSet());

assertThat(merged)
  .contains("Juventus Ronaldo Serie A", "Barcelona Messi La Liga", 
    "Liverpool Salah Premier League", "PSG");

3.5. mergeToList()

3.5.mergeToList()

mergeToList() takes multiple streams as input. It combines the value of the same index from each stream into a List:

mergeToList()接受多个数据流作为输入。它将每个流中相同索引的值合并成一个List

Stream<String> streamOfClubs = Stream
  .of("Juventus", "Barcelona", "PSG");
Stream<String> streamOfPlayers = Stream
  .of("Ronaldo", "Messi");

Stream<List<String>> mergedStreamOfList = StreamUtils
  .mergeToList(streamOfClubs, streamOfPlayers);
List<List<String>> mergedListOfList = mergedStreamOfList
  .collect(Collectors.toList());

assertThat(mergedListOfList.get(0))
  .containsExactly("Juventus", "Ronaldo");
assertThat(mergedListOfList.get(1))
  .containsExactly("Barcelona", "Messi");
assertThat(mergedListOfList.get(2))
  .containsExactly("PSG");

3.6. interleave()

3.6.interleave()

interleave() creates alternates values taken from multiple streams using a selector.

interleave() 使用selector创建取自多个流的交替值。

The method gives a set containing one value from each stream to the selector, and the selector will select one value.

该方法向选择器提供一个包含每个流的一个值的集合,选择器将选择一个值。

Then the selected value will be removed from the set and replaced with the next value from which the selected value originated. This iteration continues until all sources run out of values.

然后,所选的值将从集合中删除,并由所选值的下一个值取代。这种迭代一直持续到所有来源的数值用完为止。

The next example uses interleave() to create alternating values with a round-robin strategy:

下一个例子使用interleave()来创建具有round-robin策略的交替值。

Stream<String> streamOfClubs = Stream
  .of("Juventus", "Barcelona", "Liverpool");
Stream<String> streamOfPlayers = Stream
  .of("Ronaldo", "Messi");
Stream<String> streamOfLeagues = Stream
  .of("Serie A", "La Liga");

List<String> interleavedList = StreamUtils
  .interleave(Selectors.roundRobin(), streamOfClubs, streamOfPlayers, streamOfLeagues)
  .collect(Collectors.toList());
  
assertThat(interleavedList)
  .hasSize(7)
  .containsExactly("Juventus", "Ronaldo", "Serie A", "Barcelona", "Messi", "La Liga", "Liverpool");

Be aware that the above code is for tutorial purpose because round-robin selector is provided by the library as Selectors.roundRobin().

请注意,上面的代码是用于教程的,因为round-robin 选择器是由库提供的Selectors.roundRobin()

3.7. skipUntil() and skipWhile()

3.7.skipUntil()skipWhile()

skipUntil() skips the values until a value meets the condition:

skipUntil()跳过这些值直到一个值满足条件

Integer[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List skippedUntilGreaterThan5 = StreamUtils
  .skipUntil(stream(numbers), i -> i > 5)
  .collect(Collectors.toList());
 
assertThat(skippedUntilGreaterThan5).containsExactly(6, 7, 8, 9, 10);

In contrast, skipWhile() skips the values while the values meet the condition:

相反,skipWhile() 在数值满足条件时跳过这些数值

Integer[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List skippedWhileLessThanEquals5 = StreamUtils
  .skipWhile(stream(numbers), i -> i <= 5 || )
  .collect(Collectors.toList());
 
assertThat(skippedWhileLessThanEquals5).containsExactly(6, 7, 8, 9, 10);

One important thing about skipWhile() is that it will continue streaming after it found the first value that does not meet the condition:

关于skipWhile()的一件重要事情是,它在找到第一个不符合条件的值后会继续流转。

List skippedWhileGreaterThan5 = StreamUtils
  .skipWhile(stream(numbers), i -> i > 5)
  .collect(Collectors.toList());
assertThat(skippedWhileGreaterThan5).containsExactly(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

In Java 9 onward, dropWhile() in standard Stream API provides the same functionality as skipWhile().

在Java 9以后,标准Stream API中的dropWhile()/a>提供了与skipWhile()相同的功能。

3.8. unfold()

3.8.unfold()

unfold() generates a potentially infinite stream by applying a custom generator to a seed value and then to each generated value – the stream can be terminated by returning Optional.empty():

unfold()通过对一个种子值应用自定义生成器,然后对每个生成的值应用自定义生成器来生成一个潜在的无限流–该流可以通过返回Optional.empty()来终止:

Stream<Integer> unfolded = StreamUtils
  .unfold(2, i -> (i < 100) 
    ? Optional.of(i * i) : Optional.empty());

assertThat(unfolded.collect(Collectors.toList()))
  .containsExactly(2, 4, 16, 256);

3.9. windowed()

3.9.windowed()

windowed() creates multiple subsets of source stream as a stream of ListThe method takes a source stream, window size and skip value as the parameter.

windowed() 创建源流的多个子集作为List的流。该方法需要一个源流、窗口大小跳过值作为参数。

The List length equals window size, while skip value determines where the subset begin relative to the previous subset:

List长度等于window大小,而skip值决定了子集相对于前一个子集的开始位置。

Integer[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };

List<List> windowedWithSkip1 = StreamUtils
  .windowed(stream(numbers), 3, 1)
  .collect(Collectors.toList());
assertThat(windowedWithSkip1)
  .containsExactly(asList(1, 2, 3), asList(2, 3, 4), asList(3, 4, 5), asList(4, 5, 6), asList(5, 6, 7));

In addition, the last window is guaranteed to be of the desired size, as we can see in the following example:

此外,最后一个窗口被保证为所需的大小,我们可以在下面的例子中看到。

List<List> windowedWithSkip2 = StreamUtils.windowed(stream(numbers), 3, 2).collect(Collectors.toList());
assertThat(windowedWithSkip2).containsExactly(asList(1, 2, 3), asList(3, 4, 5), asList(5, 6, 7));

3.10. aggregate()

3.10.aggregate()

There are two aggregate() methods that work quite differently.

有两个aggregate()方法,它们的工作方式完全不同。

The first aggregate() groups together elements of equal value according to a given predicate:

第一个aggregate()根据一个给定的谓词,将具有相同价值的元素组合在一起

Integer[] numbers = { 1, 2, 2, 3, 4, 4, 4, 5 };
List<List> aggregated = StreamUtils
  .aggregate(Arrays.stream(numbers), (int1, int2) -> int1.compareTo(int2) == 0)
  .collect(Collectors.toList());
assertThat(aggregated).containsExactly(asList(1), asList(2, 2), asList(3), asList(4, 4, 4), asList(5));

The predicate receives the values in a contiguous manner. Therefore, the above will give a different result if the number is not ordered.

该谓词以连续的方式接收数值。因此,如果数字不是有序的,上面会给出不同的结果。

On the other hand, the second aggregate() is simply used to group together elements from the source stream into groups of the desired size:

另一方面,第二个aggregate()只是用来将源流中的元素组合成所需大小的组

List<List> aggregatedFixSize = StreamUtils
  .aggregate(stream(numbers), 5)
  .collect(Collectors.toList());
assertThat(aggregatedFixSize).containsExactly(asList(1, 2, 2, 3, 4), asList(4, 4, 5));

3.11. aggregateOnListCondition()

3.11.aggregateOnListCondition()

aggregateOnListCondition() groups values based on predicate and current active group. The predicate is given the currently active group as a List and the next value. It then must determine if the group should continue or start a new group.

aggregateOnListCondition()根据谓词和当前活动组将值分组。谓词被赋予当前活动组作为List和下一个值。然后它必须确定该组是否应该继续或开始一个新的组。

The following example solves a requirement to group contiguous integer values together in a group, where the sum of values in each group must not be greater than 5:

下面的例子解决了将连续的整数值归为一组的要求,其中每组中的数值之和不得大于5。

Integer[] numbers = { 1, 1, 2, 3, 4, 4, 5 };
Stream<List<Integer>> aggregated = StreamUtils
  .aggregateOnListCondition(stream(numbers), 
    (currentList, nextInt) -> currentList.stream().mapToInt(Integer::intValue).sum() + nextInt <= 5);
assertThat(aggregated)
  .containsExactly(asList(1, 1, 2), asList(3), asList(4), asList(4), asList(5));

4. Streamable<T>

4.可流动<T>

An instance of Stream isn’t reusable. For this reason, Streamable provides reusable streams by wrapping and exposing the same methods as the Stream:

Stream的一个实例是不可重用的。出于这个原因,Streamable通过包装和公开与Stream相同的方法来提供可重用的流:

Streamable<String> s = Streamable.of("a", "b", "c", "d");
List<String> collected1 = s.collect(Collectors.toList());
List<String> collected2 = s.collect(Collectors.toList());
assertThat(collected1).hasSize(4);
assertThat(collected2).hasSize(4);

5. CollectorUtils

5.CollectorUtils

CollectorUtils complements the standard Collectors by adding several useful collector methods.

CollectorUtils通过添加几个有用的收集器方法来补充标准Collectors

5.1. maxBy() and minBy()

5.1.maxBy()minBy()

maxBy() finds the maximum value in a stream using supplied projection logic:

maxBy() 使用提供的投影逻辑找到一个流中的最大值

Stream<String> clubs = Stream.of("Juventus", "Barcelona", "PSG");
Optional<String> longestName = clubs.collect(CollectorUtils.maxBy(String::length));
assertThat(longestName).contains("Barcelona");

In contrast, minBy() finds the minimum value using the supplied projection logic.

相反,minBy() 使用提供的投影逻辑找到最小值

5.2. unique()

5.2.unique()

The unique() collector does a very simple thing: it returns the only value if a given stream has exactly 1 element:

unique()收集器做了一件非常简单的事情:如果一个给定的流正好有1个元素,它返回唯一的值:

Stream<Integer> singleElement = Stream.of(1);
Optional<Integer> unique = singleElement.collect(CollectorUtils.unique());
assertThat(unique).contains(1);

Otherwise, unique() will throw an exception:

否则,unique()将抛出一个异常。

Stream multipleElement = Stream.of(1, 2, 3);
assertThatExceptionOfType(NonUniqueValueException.class).isThrownBy(() -> {
    multipleElement.collect(CollectorUtils.unique());
});

6. Conclusion

6.结论

In this article, we learned how Protonpack library expands the Java Stream API to make it easier to use. It adds useful methods that we might commonly use but are missing from the standard API.

在这篇文章中,我们了解到Protonpack库是如何扩展Java流API,使其更容易使用的。它增加了一些我们可能常用但在标准API中缺失的有用方法。

Starting with Java 9, some of the functionality provided by Protonpack will be available in the standard Stream API.

从Java 9开始,Protonpack提供的一些功能将在标准Stream API中提供。

As usual, the code can be found over on Github.

像往常一样,代码可以在Github上找到over