Guia para coletores do Java 8
1. Visão geral
Neste tutorial, iremos percorrer os coletores do Java 8, que são usados na etapa final de processamento de umStream.
Se você quiser ler mais sobre a APIStream em si, verifiquethis article.
2. O MétodoStream.collect()
Stream.collect() é um dos métodos de terminalStream API do Java 8. Ele permite realizar operações de dobra mutáveis (reempacotando elementos para algumas estruturas de dados e aplicando alguma lógica adicional, concatenando-os, etc.) em elementos de dados mantidos em uma instânciaStream.
A estratégia para esta operação é fornecida por meio da implementação da interfaceCollector.
3. Collectors
Todas as implementações predefinidas podem ser encontradas na classeCollectors. É uma prática comum usar uma importação estática a seguir com eles para aumentar a legibilidade:
import static java.util.stream.Collectors.*;
ou apenas coletores de importação de sua escolha:
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
Nos exemplos a seguir, reutilizaremos a seguinte lista:
List givenList = Arrays.asList("a", "bb", "ccc", "dd");
3.1. Collectors.toList()
O coletorToList pode ser usado para coletar todos os elementosStream em uma instânciaList. O importante a lembrar é o fato de que não podemos assumir nenhuma implementação deList em particular com este método. Se você quiser ter mais controle sobre isso, usetoCollection.
Vamos criar uma instânciaStream representando uma sequência de elementos e coletá-los em uma instânciaList:
List result = givenList.stream()
.collect(toList());
3.2. Collectors.toSet()
O coletorToSet pode ser usado para coletar todos os elementosStream em uma instânciaSet. O importante a lembrar é o fato de que não podemos assumir nenhuma implementação deSet em particular com este método. Se quisermos ter mais controle sobre isso, podemos usartoCollection.
Vamos criar uma instânciaStream representando uma sequência de elementos e coletá-los em uma instânciaSet:
Set result = givenList.stream()
.collect(toSet());
ASet não contém elementos duplicados. Se nossa coleção contiver elementos iguais uns aos outros, eles aparecerão noSet resultante apenas uma vez:
List listWithDuplicates = Arrays.asList("a", "bb", "c", "d", "bb");
Set result = listWithDuplicates.stream().collect(toSet());
assertThat(result).hasSize(4);
3.3. Collectors.toCollection()
Como você provavelmente já percebeu, ao usar coletorestoSet and toList, você não pode fazer nenhuma suposição de suas implementações. Se quiser usar uma implementação customizada, você precisará usar o coletortoCollection com uma coleção fornecida de sua escolha.
Vamos criar uma instânciaStream representando uma sequência de elementos e coletá-los em uma instânciaLinkedList:
List result = givenList.stream()
.collect(toCollection(LinkedList::new))
Observe que isso não funcionará com nenhuma coleção imutável. Nesse caso, você precisaria escrever uma implementaçãoCollector customizada ou usarcollectingAndThen.
3.4. Collectors.toMap()
O coletorToMap pode ser usado para coletar elementosStream em uma instânciaMap. Para fazer isso, precisamos fornecer duas funções:
-
keyMapper
-
valueMapper
keyMapper será usado para extrair uma chaveMap de um elementoStream, evalueMapper será usado para extrair um valor associado a uma determinada chave.
Vamos coletar esses elementos em umMap que armazena strings como chaves e seus comprimentos como valores:
Map result = givenList.stream()
.collect(toMap(Function.identity(), String::length))
Function.identity() é apenas um atalho para definir uma função que aceita e retorna o mesmo valor.
O que acontece se nossa coleção contiver elementos duplicados? Ao contrário detoSet,toMap não filtra duplicatas silenciosamente. É compreensível - como ele deve descobrir qual valor escolher para esta chave?
List listWithDuplicates = Arrays.asList("a", "bb", "c", "d", "bb");
assertThatThrownBy(() -> {
listWithDuplicates.stream().collect(toMap(Function.identity(), String::length));
}).isInstanceOf(IllegalStateException.class);
Observe quetoMap nem mesmo avalia se os valores também são iguais. Se ele vir chaves duplicadas, ele lançará imediatamente umIllegalStateException.
Nesses casos, com colisão de chaves, devemos usartoMap com outra assinatura:
Map result = givenList.stream()
.collect(toMap(Function.identity(), String::length, (item, identicalItem) -> item));
O terceiro argumento aqui éBinaryOperator, onde podemos especificar como queremos que as colisões sejam tratadas. Nesse caso, vamos escolher qualquer um desses dois valores em conflito, porque sabemos que as mesmas strings sempre terão os mesmos comprimentos também.
3.5. Collectors.c_ollectingAndThen () _
CollectingAndThen é um coletor especial que permite realizar outra ação em um resultado logo após as pontas de coleta.
Vamos coletarStream elementos para uma instânciaList e, em seguida, converter o resultado em uma instânciaImmutableList:
List result = givenList.stream()
.collect(collectingAndThen(toList(), ImmutableList::copyOf))
3.6. Collectors.j_oining () _
O coletorJoining pode ser usado para unir os elementosStream<String>.
Podemos juntá-los fazendo:
String result = givenList.stream()
.collect(joining());
o que resultará em:
"abbcccdd"
Você também pode especificar separadores personalizados, prefixos, correções posteriores:
String result = givenList.stream()
.collect(joining(" "));
o que resultará em:
"a bb ccc dd"
ou você pode escrever:
String result = givenList.stream()
.collect(joining(" ", "PRE-", "-POST"));
o que resultará em:
"PRE-a bb ccc dd-POST"
3.7. Collectors.c_ounting () _
Counting é um coletor simples que permite simplesmente contar todos os elementosStream.
Agora podemos escrever:
Long result = givenList.stream()
.collect(counting());
3.8. Collectors.s_ummarizingDouble/Long/Int()_
SummarizingDouble/Long/Int é um coletor que retorna uma classe especial contendo informações estatísticas sobre dados numéricos emStream de elementos extraídos.
Podemos obter informações sobre o comprimento das strings fazendo:
DoubleSummaryStatistics result = givenList.stream()
.collect(summarizingDouble(String::length));
Nesse caso, o seguinte será verdadeiro:
assertThat(result.getAverage()).isEqualTo(2);
assertThat(result.getCount()).isEqualTo(4);
assertThat(result.getMax()).isEqualTo(3);
assertThat(result.getMin()).isEqualTo(1);
assertThat(result.getSum()).isEqualTo(8);
3.9. Collectors.averagingDouble/Long/Int()
AveragingDouble/Long/Int é um coletor que simplesmente retorna uma média dos elementos extraídos.
Podemos obter um comprimento médio de string fazendo:
Double result = givenList.stream()
.collect(averagingDouble(String::length));
3.10. Collectors.s_ummingDouble/Long/Int()_
SummingDouble/Long/Int é um coletor que simplesmente retorna uma soma dos elementos extraídos.
Podemos obter uma soma de todos os comprimentos de string fazendo:
Double result = givenList.stream()
.collect(summingDouble(String::length));
3.11. Collectors.maxBy()/minBy()
MaxBy/MinBy collectors return the biggest/the smallest element of a Stream according to a provided Comparator instance.
Podemos escolher o maior elemento fazendo:
Optional result = givenList.stream()
.collect(maxBy(Comparator.naturalOrder()));
Observe que o valor retornado está empacotado em uma instânciaOptional. Isso força os usuários a repensar a caixa de canto da coleção vazia.
3.12. Collectors.groupingBy()
O coletorGroupingBy é usado para agrupar objetos por alguma propriedade e armazenar resultados em uma instânciaMap.
Podemos agrupá-los por comprimento de string e armazenar resultados de agrupamento em instânciasSet:
Map> result = givenList.stream()
.collect(groupingBy(String::length, toSet()));
Isso resultará no seguinte:
assertThat(result)
.containsEntry(1, newHashSet("a"))
.containsEntry(2, newHashSet("bb", "dd"))
.containsEntry(3, newHashSet("ccc"));
Observe que o segundo argumento do métodogroupingBy é umCollectore você está livre para usar qualquerCollector de sua escolha.
3.13. Collectors.partitioningBy()
PartitioningBy é um caso especializado degroupingBy que aceita uma instânciaPredicate e coleta elementosStream em uma instânciaMap que armazena valoresBoolean como chaves e coleções como valores. Na chave “verdadeira”, você pode encontrar uma coleção de elementos que correspondem aoPredicate fornecido, e na chave “falsa”, você pode encontrar uma coleção de elementos que não combinam com oPredicate fornecido.
Você pode escrever:
Map> result = givenList.stream()
.collect(partitioningBy(s -> s.length() > 2))
O que resulta em um mapa contendo:
{false=["a", "bb", "dd"], true=["ccc"]}
3.14. Collectors.teeing()
Vamos encontrar os números máximo e mínimo de um determinadoStream usando os coletores que aprendemos até agora:
List numbers = Arrays.asList(42, 4, 2, 24);
Optional min = numbers.stream().collect(minBy(Integer::compareTo));
Optional max = numbers.stream().collect(maxBy(Integer::compareTo));
// do something useful with min and max
Aqui, estamos usando dois coletores diferentes e combinando o resultado desses dois para criar algo significativo. Antes do Java 12, a fim de cobrir tais casos de uso, tínhamos que operar noStream fornecido duas vezes, armazenar os resultados intermediários em variáveis temporárias e, em seguida, combinar esses resultados depois.
Felizmente, o Java 12 oferece um coletor interno que cuida dessas etapas em nosso nome: tudo o que precisamos fazer é fornecer os dois coletores e a função combinadora.
Uma vez que este novo coletortees o fluxo fornecido em duas direções diferentes, ele é chamado deteeing:
numbers.stream().collect(teeing(
minBy(Integer::compareTo), // The first collector
maxBy(Integer::compareTo), // The second collector
(min, max) -> // Receives the result from those collectors and combines them
));
Este exemplo está disponível no GitHub no projetocore-java-12.
4. Coletores personalizados
Se você deseja gravar sua implementação do Collector, é necessário implementar a interface do Collector e especificar seus três parâmetros genéricos:
public interface Collector {...}
-
T - o tipo de objetos que estarão disponíveis para coleta,
-
A - o tipo de um objeto acumulador mutável,
-
R - o tipo de resultado final.
Vamos escrever um exemplo de coletor para coletar elementos em uma instânciaImmutableSet. Começamos especificando os tipos certos:
private class ImmutableSetCollector
implements Collector, ImmutableSet> {...}
Como precisamos de uma coleção mutável para manipulação de operação de coleção interna, não podemos usarImmutableSet para isso; precisamos usar alguma outra coleção mutável ou qualquer outra classe que possa acumular objetos temporariamente para nós. Nesse caso, continuaremos com umImmutableSet.Buildere agora precisamos implementar 5 métodos:
-
Fornecedor
>supplier () -
BiConsumer
, T>accumulator () -
BinaryOperator
>combiner () -
Função
, ImmutableSet >finisher () -
Defina
characteristics ()
O métodoThe supplier() retorna uma instânciaSupplier que gera uma instância de acumulador vazia, então, neste caso, podemos simplesmente escrever:
@Override
public Supplier> supplier() {
return ImmutableSet::builder;
}
O métodoThe accumulator() retorna uma função que é usada para adicionar um novo elemento a um objetoaccumulator existente, então vamos apenas usar o métodoBuilder'sadd.
@Override
public BiConsumer, T> accumulator() {
return ImmutableSet.Builder::add;
}
O métodoThe combiner() retorna uma função que é usada para mesclar dois acumuladores:
@Override
public BinaryOperator> combiner() {
return (left, right) -> left.addAll(right.build());
}
O métodoThe finisher() retorna uma função que é usada para converter um acumulador para o tipo de resultado final, então, neste caso, usaremos apenas o métodoBuilder'sbuild:
@Override
public Function, ImmutableSet> finisher() {
return ImmutableSet.Builder::build;
}
O métodoThe characteristics() é usado para fornecer ao Stream algumas informações adicionais que serão usadas para otimizações internas. Nesse caso, não prestamos atenção à ordem dos elementos em aSet, de modo que usaremosCharacteristics.UNORDERED. Para obter mais informações sobre este assunto, verifiqueCharacteristics ‘JavaDoc.
@Override public Set characteristics() {
return Sets.immutableEnumSet(Characteristics.UNORDERED);
}
Aqui está a implementação completa junto com o uso:
public class ImmutableSetCollector
implements Collector, ImmutableSet> {
@Override
public Supplier> supplier() {
return ImmutableSet::builder;
}
@Override
public BiConsumer, T> accumulator() {
return ImmutableSet.Builder::add;
}
@Override
public BinaryOperator> combiner() {
return (left, right) -> left.addAll(right.build());
}
@Override
public Function, ImmutableSet> finisher() {
return ImmutableSet.Builder::build;
}
@Override
public Set characteristics() {
return Sets.immutableEnumSet(Characteristics.UNORDERED);
}
public static ImmutableSetCollector toImmutableSet() {
return new ImmutableSetCollector<>();
}
e aqui em ação:
List givenList = Arrays.asList("a", "bb", "ccc", "dddd");
ImmutableSet result = givenList.stream()
.collect(toImmutableSet());
5. Conclusão
Neste artigo, exploramos em detalhes Java 8Collectorse mostramos como implementá-lo. Certifique-se decheck one of my projects which enhances the capabilities of parallel processing in Java.
Todos os exemplos de código estão disponíveis emGitHub. Você pode ler mais artigos interessanteson my site.