Introduction à Protonpack

Introduction à Protonpack

1. Vue d'ensemble

Dans ce didacticiel, nous allons examiner les principales fonctionnalités deProtonpack, qui est une bibliothèque qui étend lesStream API standard en ajoutant des fonctionnalités complémentaires.

Reportez-vous àthis write-up here pour découvrir les principes de base de l'API JavaStream.

2. Dépendance Maven

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


    com.codepoetics
    protonpack
    1.15

Recherchez la dernière version surMaven Central.

3. StreamUtils

C'est la classe principale qui étend l'API standard de JavaStream.

Toutes les méthodes discutées ici sontintermediate operations, ce qui signifie qu’elles modifient unStream mais ne déclenchent pas son traitement.

3.1. takeWhile() ettakeUntil()

takeWhile() prend les valeurs du flux sourceas 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);

Inversement,takeUntil() prend les valeursuntil a value meets supplied condition 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 desStream API standard.

3.2. zip()

zip() prend deux ou trois flux comme entrée et comme fonction de combinaison. La méthodetakes a value from the same position of each stream and passes it to the combiner.

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, unzip() surchargé qui prend trois flux sources:

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

merge() fonctionne avec plusieurs flux sources et un combineur. Iltakes the value of the same index position from each source stream and passes it to the combiner.

La méthode fonctionne en prenant une valeur du même index à partir de chaque flux successivement, à partir de la valeurseed.

La valeur est ensuite 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. Ilcombines 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.

La méthode donne un ensemble contenant une valeur de chaque flux auxselector, et lesselector sélectionneront une valeur.

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

L'exemple suivant utiliseinterleave() pour créer des valeurs alternées avec une stratégieround-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");

Sachez que le code ci-dessus est à des fins de didacticiel car le round-robinselector  est fourni par la bibliothèque en tant queSelectors.roundRobin().

3.7. skipUntil() ableskipWhile()

skipUntil() ignore les valeursuntil 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);

En revanche,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);

Une chose importante à propos deskipWhile()  est qu'il continuera à diffuser après avoir trouvé la première valeur qui ne remplit pas la 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);

À partir de Java 9,dropWhile() dans l'API standardStream fournit les mêmes fonctionnalités queskipWhile().

3.8. unfold()

unfold() génère un flux potentiellement infini en appliquant un générateur personnalisé à une valeur de départ puis à chaque valeur générée - le flux peut être arrêté en renvoyantOptional.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*.  La méthode prend un flux source,window size etskip value comme paramètre.

La longueur deList est égale àwindowsize, while skip value détermine où le sous-ensemble commence par rapport au sous-ensemble précédent:

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

De plus, la dernière fenêtre a la taille souhaitée, comme dans l'exemple suivant:

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

Il existe deux méthodesaggregate() qui fonctionnent très différemment.

Les premiersaggregate() 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));

Le prédicat reçoit les valeurs de manière contiguë. Par conséquent, ce qui précède donnera un résultat différent si le nombre n’est pas commandé.

Par contre, le secondaggregate() est simplement utilisé pourgroup 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() regroupe les valeursbased on predicate and current active group. Le prédicat reçoit le groupe actuellement actif sous forme deList et la valeur suivante. Il doit ensuite déterminer si le groupe doit continuer ou créer un nouveau groupe.

L'exemple suivant résout la nécessité de regrouper des valeurs entières contiguës dans un groupe, la somme des valeurs de chaque groupe ne devant pas être supérieure à 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>

Une instance deStream n'est pas réutilisable. Pour cette raison,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 complète lesCollectors standard en ajoutant plusieurs méthodes de collecteur utiles.

5.1. maxBy() ableminBy()

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

En revanche,minBy()finds the minimum value using the supplied projection logic.

5.2. unique()

Le collecteurunique() fait une chose très simple: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);

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 développait l'API Java Stream pour la rendre plus facile à utiliser. Il ajoute des méthodes utiles que nous pourrions utiliser couramment mais qui sont absentes de 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éover on Github.