Introduzione a Protonpack

1. Panoramica

In questo tutorial, esamineremo le principali funzionalità di Protonpack, una libreria che espande l' API Stream standard aggiungendo alcune funzionalità complementari.

Fare riferimento a questo articolo qui per scoprire i fondamenti dell'API Java Stream .

2. Dipendenza da Maven

Per utilizzare la libreria Protonpack, dobbiamo aggiungere una dipendenza nel nostro file pom.xml :

 com.codepoetics protonpack 1.15 

Verificare la versione più recente su Maven Central.

3. StreamUtils

Questa è la classe principale che espande l' API Stream standard di Java .

Tutti i metodi discussi qui sono operazioni intermedie, il che significa che modificano un flusso ma non ne attivano l'elaborazione.

3.1. takeWhile () e takeUntil ()

takeWhile () prende i valori dal flusso di origine purché soddisfino la condizione fornita :

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

Al contrario, takeUntil () assume valori finché un valore non soddisfa la condizione fornita e quindi si ferma:

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

In Java 9 in poi, takeWhile () fa parte dell'API Stream standard .

3.2. cerniera lampo()

zip () accetta due o tre flussi come input e una funzione combinatore. Il metodo prende un valore dalla stessa posizione di ogni flusso e lo passa al combinatore .

Lo fa fino a quando uno dei flussi non esaurisce i valori:

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

Allo stesso modo, un zip () sovraccarico che accetta tre flussi di fonti:

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 () accetta valori e comprime ogni valore con il suo indice per creare un flusso di valori indicizzati:

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

merge () funziona con più flussi di origine e un combinatore. Si prende il valore della stessa posizione di indice da ogni flusso di fonti e lo passa al combinatore .

Il metodo funziona prendendo 1 valore dallo stesso indice da ogni stream in successione, a partire dal valore seed .

Quindi il valore viene passato al combinatore e il valore combinato risultante viene reindirizzato al combinatore per creare il valore successivo:

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 () accetta più flussi come input. Esso combina il valore dello stesso indice da ogni flusso in un elenco :

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 () crea valori alternativi presi da più flussi utilizzando un selettore .

Il metodo fornisce un set contenente un valore da ogni flusso al selettore e il selettore selezionerà un valore.

Quindi il valore selezionato verrà rimosso dal set e sostituito con il valore successivo da cui ha avuto origine il valore selezionato. Questa iterazione continua fino a quando tutte le origini esauriscono i valori.

Il prossimo esempio utilizza interleave () per creare valori alternati con una strategia 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"); 

Tieni presente che il codice precedente è a scopo di esercitazione perché il selettore round robin è fornito dalla libreria come Selectors.roundRobin () .

3.7. skipUntil () e skipWhile ()

skipUntil () salta i valori finché un valore non soddisfa la condizione :

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

Al contrario, skipWhile () salta i valori mentre i valori soddisfano la condizione :

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

Il raccoglitore unique () fa una cosa molto semplice: restituisce l'unico valore se un dato flusso ha esattamente 1 elemento:

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

Altrimenti, unique () genererà un'eccezione:

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

6. Conclusione

In questo articolo, abbiamo appreso come la libreria Protonpack espande l'API Java Stream per renderla più facile da usare. Aggiunge metodi utili che potremmo usare comunemente ma che mancano nell'API standard.

A partire da Java 9, alcune delle funzionalità fornite da Protonpack saranno disponibili nell'API Stream standard.

Come al solito, il codice può essere trovato su Github.