Введение в Protonpack

Введение в Protonpack

1. обзор

В этом руководстве мы рассмотрим основные функцииProtonpack, библиотеки, которая расширяет стандартныйStream API, добавляя некоторые дополнительные функции.

Обратитесь кthis write-up here, чтобы узнать основы API JavaStream.

2. Maven Dependency

Чтобы использовать библиотеку Protonpack, нам нужно добавить зависимость в наш файлpom.xml:


    com.codepoetics
    protonpack
    1.15

Проверьте последнюю версию наMaven Central.

3. StreamUtilsс

Это основной класс, расширяющий стандартный API JavaStream.

Все обсуждаемые здесь методы -intermediate operations, что означает, что они изменяютStream, но не запускают его обработку.

3.1. takeWhile() иtakeUntil()

takeWhile() принимает значения из исходного потокаas long as they meet the supplied condition:

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

И наоборот,takeUntil() принимает значенияuntil a value meets supplied condition и затем останавливается:

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

Начиная с Java 9,takeWhile() является частью стандартногоStream API.

3.2. zip()с

zip() принимает два или три потока в качестве входных данных и функцию объединения. Методtakes a value from the same position of each stream and passes it to the combiner.

Это происходит до тех пор, пока один из потоков не исчерпает значения:

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

assertThat(zippedFrom2Sources)
  .contains("Juventus Ronaldo", "Barcelona Messi", "Liverpool Salah");

Точно так же перегруженныйzip(), который принимает поток из трех источников:

String[] leagues = { "Serie A", "La Liga", "Premier League" };
Set 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()с

zipWithIndex () принимает значения и связывает каждое значение с его индексом, чтобы создать поток индексированных значений:

Stream streamOfClubs = Stream
  .of("Juventus", "Barcelona", "Liverpool");
Set> 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()с

merge() работает с несколькими исходными потоками и объединителем. Этоtakes the value of the same index position from each source stream and passes it to the combiner.

Метод работает путем последовательного получения 1 значения из одного и того же индекса из каждого потока, начиная со значенияseed.

Затем значение передается объединителю, а полученное объединенное значение возвращается обратно в объединитель для создания следующего значения:

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

Set 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()с

mergeToList() принимает в качестве входных данных несколько потоков. Этоcombines the value of the same index from each stream into a List:

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

Stream> mergedStreamOfList = StreamUtils
  .mergeToList(streamOfClubs, streamOfPlayers);
List> 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()с

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

Метод дает набор, содержащий одно значение из каждого потока дляselector, аselector выберет одно значение.

Затем выбранное значение будет удалено из набора и заменено следующим значением, из которого получено выбранное значение. Эта итерация продолжается до тех пор, пока во всех источниках не закончатся значения.

В следующем примереinterleave() to создает чередующиеся значения со стратегиейround-robin:

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

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

Имейте в виду, что приведенный выше код предназначен для учебных целей, поскольку циклический алгоритмselector  предоставляется библиотекой какSelectors.roundRobin().

3.7. skipUntil() песокskipWhile()

skipUntil() пропускает значенияuntil a value meets the condition:

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

Напротив,skipWhile()skips the values while the values meet the condition:

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

Одна важная вещь о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);

Начиная с Java 9,dropWhile() в стандартном APIStream предоставляет те же функции, что иskipWhile().

3.8. unfold()с

unfold() генерирует потенциально бесконечный поток, применяя пользовательский генератор к начальному значению, а затем к каждому сгенерированному значению - поток можно завершить, вернувOptional.empty():

Stream 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()с

windowed()creates multiple subsets of source stream as a stream of *List*.  Метод принимает исходный поток,window size иskip value в качестве параметра.

ДлинаList равнаwindowsize, , а skip value определяет, где начинается подмножество относительно предыдущего подмножества:

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

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

Кроме того, последнее окно гарантированно будет иметь желаемый размер, как мы можем видеть в следующем примере:

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

Есть два методаaggregate() , которые работают по-разному.

Первыйaggregate() groups together elements of equal value according to a given predicate:

Integer[] numbers = { 1, 2, 2, 3, 4, 4, 4, 5 };
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));

Предикат получает значения непрерывным образом. Следовательно, вышеприведенное даст другой результат, если номер не заказан.

С другой стороны, второйaggregate() просто используется дляgroup together elements from the source stream into groups of the desired size:

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

aggregateOnListCondition() группирует значенияbased on predicate and current active group. Предикату дается текущая активная группа какList и следующее значение. Затем он должен определить, должна ли группа продолжить или начать новую группу.

В следующем примере решается требование сгруппировать непрерывные целочисленные значения в группу, где сумма значений в каждой группе не должна превышать 5:

Integer[] numbers = { 1, 1, 2, 3, 4, 4, 5 };
Stream> 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>с

ЭкземплярStream  нельзя использовать повторно. По этой причинеStreamable provides reusable streams by wrapping and exposing the same methods as the Stream:

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

5. CollectorUtilsс

CollectorUtils дополняет стандартныйCollectors, добавляя несколько полезных методов сборщика.

5.1. maxBy() песокminBy()

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

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

Напротив,minBy()finds the minimum value using the supplied projection logic.

5.2. unique()с

Сборщикunique() делает очень простую вещь:it returns the only value if a given stream has exactly 1 element:

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

В противном случаеunique() вызовет исключение:

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

6. Заключение

В этой статье мы узнали, как библиотека Protonpack расширяет API Java Stream, чтобы упростить его использование. Он добавляет полезные методы, которые мы обычно используем, но отсутствуют в стандартном API.

Начиная с Java 9, некоторые функции, предоставляемые Protonpack, будут доступны в стандартном Stream API.

Как обычно, можно найти кодover on Github.