Въведение в Protonpack

1. Общ преглед

В този урок ще разгледаме основните характеристики на Protonpack, която е библиотека, която разширява стандартния API на Stream, като добавя някои безплатни функции.

Обърнете се към това описание тук, за да откриете основите на Java Stream API.

2. Зависимост на Maven

За да използваме библиотеката Protonpack, трябва да добавим зависимост в нашия файл pom.xml :

 com.codepoetics protonpack 1.15 

Проверете за най-новата версия на Maven Central.

3. StreamUtils

Това е основният клас, който разширява стандартния Java Stream API.

Всички разгледани тук методи са междинни операции, което означава, че те модифицират поток, но не задействат обработката му.

3.1. takeWhile () и takeUntil ()

takeWhile () приема стойности от потока източник , стига да отговарят на предоставеното условие :

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 () приема стойности, докато стойността отговаря на предоставеното условие и след това спира:

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 () е част от стандартния API на Stream .

3.2. цип ()

zip () приема два или три потока като вход и комбинираща функция. Методът взема стойност от една и съща позиция на всеки поток и я предава на комбинатора .

Прави това, докато един от потоците не изтече стойности:

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 () работи с множество потоци източник и комбинатор. Той взема стойността на една и съща позиция на индекса от всеки поток източник и я предава на комбинатора .

Методът работи, като една стойност от същия индекс от всеки поток последователно, като се излиза от семена стойност.

След това стойността се предава на комбинатора и получената комбинирана стойност се връща обратно на комбинатора, за да се създаде следващата стойност:

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 () приема множество потоци като вход. Той комбинира стойността на един и същ индекс от всеки поток в Списък :

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 () създава алтернативни стойности, взети от множество потоци с помощта на селектор .

Методът дава набор, съдържащ по една стойност от всеки поток на селектора и селекторът ще избере една стойност.

Тогава избраната стойност ще бъде премахната от набора и заменена със следващата стойност, от която произхожда избраната стойност. Тази итерация продължава, докато всички източници останат без стойности.

Следващият пример използва interleave (), за да създаде редуващи се стойности със стратегия за кръгово движение :

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

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

3.7. skipUntil () и skipWhile ()

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

За разлика от това, 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:

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

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

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. The method takes a source stream, window size and skip value as the parameter.

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

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

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

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

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

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

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:

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

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:

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

An instance of Stream isn't reusable. For this reason, 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 complements the standard Collectors by adding several useful collector methods.

5.1. maxBy() and 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");

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

5.2. unique()

В уникален () колектор прави нещо много просто: тя се връща единствената стойност, ако даден поток има точно един елемент:

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 разширява Java Stream API, за да улесни използването. Той добавя полезни методи, които често използваме, но липсват в стандартния API.

Започвайки с Java 9, част от функционалността, предоставена от Protonpack, ще бъде налична в стандартния API на Stream.

Както обикновено, кодът може да бъде намерен в Github.