Introdução ao Protonpack
1. Visão geral
Neste tutorial, veremos os principais recursos deProtonpack, que é uma biblioteca que expande oStream API padrão adicionando algumas funcionalidades complementares.
Consultethis write-up here para descobrir os fundamentos da API JavaStream.
2. Dependência do Maven
Para usar a biblioteca Protonpack, precisamos adicionar uma dependência em nosso arquivopom.xml:
com.codepoetics
protonpack
1.15
Verifique a versão mais recente emMaven Central.
3. StreamUtils
Esta é a classe principal que expande a APIStream padrão do Java.
Todos os métodos discutidos aqui sãointermediate operations, o que significa que eles modificam aStream, mas não acionam seu processamento.
3.1. takeWhile() etakeUntil()
takeWhile() obtém valores do fluxo de origemas 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);
Por outro lado,takeUntil() assume os valoresuntil a value meets supplied conditione então para:
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);
No Java 9 em diante,takeWhile() faz parte doStream API padrão.
3.2. zip()
zip() leva dois ou três fluxos como uma entrada e uma função combinadora. O métodotakes a value from the same position of each stream and passes it to the combiner.
Faz isso até que um dos fluxos fique sem valores:
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");
Da mesma forma, umzip() sobrecarregado que leva três fontes de fluxo:
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 () pega valores e compara cada valor com seu índice para criar um fluxo de valores indexados:
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() funciona com vários fluxos de origem e um combinador. Étakes the value of the same index position from each source stream and passes it to the combiner.
O método funciona pegando 1 valor do mesmo índice de cada fluxo em sucessão, começando com o valorseed.
Em seguida, o valor é passado para o combinador e o valor combinado resultante é retornado ao combinador para criar o próximo valor:
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() leva vários fluxos como entrada. É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.
O método fornece um conjunto contendo um valor de cada fluxo paraselector, eselector selecionará um valor.
Em seguida, o valor selecionado será removido do conjunto e substituído pelo próximo valor do qual o valor selecionado se originou. Essa iteração continua até que todas as fontes fiquem sem valores.
O próximo exemplo usainterleave() para criar valores alternados com uma estratégiaround-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");
Esteja ciente de que o código acima é para fins de tutorial porque round-robinselector é fornecido pela biblioteca comoSelectors.roundRobin().
3.7. skipUntil() areiaskipWhile()
skipUntil() ignora os valoresuntil 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);
Em contraste,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);
Uma coisa importante sobreskipWhile() é que ele continuará transmitindo após encontrar o primeiro valor que não atende à condição:
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);
No Java 9 em diante,dropWhile() na APIStream padrão fornece a mesma funcionalidade queskipWhile().
3.8. unfold()
unfold() gera um fluxo potencialmente infinito aplicando um gerador personalizado a um valor de semente e, em seguida, a cada valor gerado - o fluxo pode ser encerrado retornandoOptional.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*. O método usa um fluxo de origem,window sizeeskip value como parâmetro.
O comprimento deList é igual awindowsize, while skip value determina onde o subconjunto começa em relação ao subconjunto anterior:
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));
Além disso, é garantido que a última janela tenha o tamanho desejado, como podemos ver no exemplo a seguir:
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()
Existem dois métodosaggregate() que funcionam de maneira bem diferente.
O primeiroaggregate() 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));
O predicado recebe os valores de maneira contígua. Portanto, o exposto acima dará um resultado diferente se o número não for solicitado.
Por outro lado, o segundoaggregate() é simplesmente usado paragroup 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() agrupa valoresbased on predicate and current active group. O predicado recebe o grupo atualmente ativo comoListe o próximo valor. Em seguida, ele deve determinar se o grupo deve continuar ou iniciar um novo grupo.
O exemplo a seguir resolve um requisito para agrupar valores inteiros contíguos em um grupo, em que a soma dos valores em cada grupo não deve ser maior que 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>
Uma instância deStream não é reutilizável. Por esse motivo,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 complementa o padrãoCollectors adicionando vários métodos de coletor úteis.
5.1. maxBy() areiaminBy()
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");
Em contraste,minBy()finds the minimum value using the supplied projection logic.
5.2. unique()
O coletorunique() faz uma coisa muito simples: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);
Caso contrário,unique() lançará uma exceção:
Stream multipleElement = Stream.of(1, 2, 3);
assertThatExceptionOfType(NonUniqueValueException.class).isThrownBy(() -> {
multipleElement.collect(CollectorUtils.unique());
});
6. Conclusão
Neste artigo, aprendemos como a biblioteca Protonpack expande a API Java Stream para facilitar o uso. Ele adiciona métodos úteis que normalmente podemos usar, mas estão ausentes na API padrão.
A partir do Java 9, algumas das funcionalidades fornecidas pelo Protonpack estarão disponíveis na API padrão do Stream.
Como de costume, o código pode ser encontradoover on Github.