Introduction à Protonpack

1. Vue d'ensemble

Dans ce didacticiel, nous examinerons les principales fonctionnalités de Protonpack, une bibliothèque qui étend l' API Stream standard en ajoutant des fonctionnalités complémentaires.

Reportez-vous à cet article ici pour découvrir les principes de base de l' API Java Stream .

2. Dépendance de Maven

Pour utiliser la bibliothèque Protonpack, nous devons ajouter une dépendance dans notre fichier pom.xml :

 com.codepoetics protonpack 1.15 

Recherchez la dernière version sur Maven Central.

3. StreamUtils

Il s'agit de la classe principale qui étend l' API Stream standard de Java .

Toutes les méthodes discutées ici sont des opérations intermédiaires, ce qui signifie qu'elles modifient un Stream mais ne déclenchent pas son traitement.

3.1. takeWhile () et takeUntil ()

takeWhile () prend les valeurs du flux source tant qu'elles remplissent la condition fournie :

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

Inversement, takeUntil () prend des valeurs jusqu'à ce qu'une valeur remplisse la condition fournie, puis s'arrête:

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

À partir de Java 9, takeWhile () fait partie de l' API Stream standard .

3.2. Zip *: français()

zip () prend deux ou trois flux comme entrée et comme fonction de combinaison. La méthode prend une valeur à la même position de chaque flux et la transmet au combineur .

Il le fait jusqu'à ce que l'un des flux soit à court de valeurs:

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

De même, un zip surchargé () qui prend trois sources de flux:

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 () prend des valeurs et zippe chaque valeur avec son index pour créer un flux de valeurs indexées:

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

merge () fonctionne avec plusieurs flux sources et un combineur. Il prend la valeur de la même position d'index de chaque flux source et la transmet au combineur .

Le procédé fonctionne en prenant une valeur à partir du même indice de chaque flux en succession, à partir de la graine de valeur.

Ensuite, la valeur est transmise au combineur et la valeur combinée résultante est renvoyée au combineur pour créer la valeur suivante:

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 () prend plusieurs flux en entrée. Il combine la valeur du même index de chaque flux dans une liste :

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

interleave () crée des valeurs alternatives prises à partir de plusieurs flux à l'aide d'un sélecteur .

La méthode donne un ensemble contenant une valeur de chaque flux au sélecteur , et le sélecteur sélectionnera une valeur.

Ensuite, la valeur sélectionnée sera supprimée de l'ensemble et remplacée par la valeur suivante dont provient la valeur sélectionnée. Cette itération se poursuit jusqu'à ce que toutes les sources soient à court de valeurs.

L'exemple suivant utilise interleave () pour créer des valeurs alternées avec une stratégie de tourniquet :

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

Sachez que le code ci-dessus est à des fins de didacticiel, car le sélecteur à tour de rôle est fourni par la bibliothèque en tant que Selectors.roundRobin () .

3.7. skipUntil () et skipWhile ()

skipUntil () ignore les valeurs jusqu'à ce qu'une valeur remplisse la 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); 

En revanche, skipWhile () ignore les valeurs alors que les valeurs remplissent la 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); 

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

Le collecteur unique () fait une chose très simple: il renvoie la seule valeur si un flux donné a exactement 1 élément:

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

Sinon, unique () lèvera une exception:

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

6. Conclusion

Dans cet article, nous avons appris comment la bibliothèque Protonpack étend l'API Java Stream pour la rendre plus facile à utiliser. Il ajoute des méthodes utiles que nous pourrions utiliser couramment mais qui manquent dans l'API standard.

À partir de Java 9, certaines des fonctionnalités fournies par Protonpack seront disponibles dans l'API Stream standard.

Comme d'habitude, le code peut être trouvé sur Github.