Introdução ao Protonpack

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.