Coleção, coleções, coleção, colecionador, Collectos
A coleção é a interface ancestral das coleções Java.
As coleções são uma classe de ferramentas no pacote java.util, que conotoca vários métodos estáticos para o processamento de coleções.
java.util.stream.stream#collect (java.util.stream.collector <? super t, a, r>) é uma função do fluxo responsável por coletar fluxos.
java.util.stream.collector é uma interface para coletar funções que declara as funções de um colecionador.
java.util.comparators é uma classe de ferramentas de colecionador com uma série de implementações de colecionadores incorporadas.
A função do colecionador
Você pode pensar nos fluxos Java8 como iteradores de dados sofisticados e preguiçosos. Eles suportam dois tipos de operações: operações intermediárias (por exemplo, filtro, mapa) e operações terminais (como contagem, findfirst, foreach, redução). As operações intermediárias podem ser conectadas para converter um fluxo em outro. Essas operações não consomem fluxos e o objetivo é criar um pipeline. Por outro lado, as operações do terminal consomem classes, resultando em um resultado final. A coleta é uma operação de redução, assim como reduzir, pode aceitar vários métodos como parâmetros e acumular elementos no fluxo em um resultado resumido. A abordagem específica é definida definindo uma nova interface de colecionador.
Colecionadores predefinidos
A seguir, é apresentada uma breve demonstração do coletor básico interno. A fonte de dados simulada é a seguinte:
Final Arraylist <Sish> prisões = lists.newArraylist (novo prato ("carne de porco", false, 800, tipo.meat), prato novo ("carne", falsa, 700, tipo.meat), novo prato ("Frango", false, 400, tipo. frutas ", verdadeiro, 120, tipo. Outro), novo prato (" pizza ", verdadeiro, 550, tipo. Outro), novo prato (" camarão ", falso, 300, tipo.fish), novo prato (" salmão ", falso, 450, tipo.fish));Valor máximo, valor mínimo, valor médio
// Por que retornar opcional? O que fazer se o fluxo for nulo? O optinal faz muito sentido neste momento opcional <dish> mais acaloriedish = Dishes.Stream (). Max (comparador.comparingint (Dish :: getCalories)); opcional <sish> MiCaloriedish = Dishes.Stream (). Min (comparador.comparingInt (Dish :: GetCalories)); Dishes.Stream (). Colete (colecionadores.AvagingInt (Dish :: getCalories)); IntsummaryStatistics SummaryStatistics = Dishes.Stream (). Colete (colecionors.summarizingInt (Dish :: getCalories)); média dupla = SummaryStatistics.Geraverage (); long count = summaryStatistics.getCount (); int max = summaryStatistics.getMax (); int min = summarystatistics.getmin (); soma longa = summarystatistics.getSum ();
Esses indicadores estatísticos simples possuem funções de coletor de colecionadores, especialmente para funções de unboxing do tipo numérico, que serão muito mais caras do que operar diretamente o tipo de embalagem.
Conecte o coletor
Quer montar os elementos do fluxo?
// conectar diretamente a string junção1 = dishes.stream (). Map (dish :: getName) .collect (collectors.Join ()); // vírgula string junção2 = Dishes.Stream (). Map (Dish :: getName) .Collect (colecionors.Join (","));tolista
Lista <String> nomes = disses.stream (). Map (dish :: getName) .collect (tolist ());
Mapeie o fluxo original em um único fluxo de elemento e colete -o como uma lista.
Toset
SET <TIPE> TIPOS = DISHES.STREAM (). MAP (DISH :: getType) .Collect (Collectors.toset ());
Colete o tipo como um conjunto e você pode repeti -lo.
Tomap
Mapa <tipo, prato> bytype = Dishes.Stream (). Colete (tomap (Dish :: getType, d -> d));
Às vezes, pode ser necessário converter uma matriz em um mapa para cache, o que facilita vários cálculos e aquisições. O TOMAP fornece as funções de geração dos métodos k e v. (Observe que a demonstração acima é um poço, você não pode usá -lo assim !!! Por favor, use o tomap (função, função, binário operador))
Os acima são quase os colecionadores mais usados e são basicamente suficientes. Mas como iniciante, o entendimento leva tempo. Para entender realmente por que isso pode ser usado para coletar, você deve verificar a implementação interna. Você pode ver que esses colecionadores são baseados em java.util.stream.collectors.collectorImpl, que é uma classe de implementação de colecionador mencionado no início. O colecionador personalizado aprenderá o uso específico posteriormente.
Redução personalizada
Os poucos anteriores são casos especiais do processo de redução definido pelo método Reduce Factory. De fato, colecionadores. A redução pode ser usada para criar um colecionador. Por exemplo, procure soma
Inteiro totalCalories = Dishes.Stream (). Colete (redução (0, Dish :: getCalories, (i, j) -> i + j)); // use a função interna em vez de flecha inteira totalCalories2 = Dishes.Stream (). Colete (redução (0, Dish :: getCalories, intereger);
Claro, você também pode usar redução diretamente
Opcional <Terger> totalCalories3 = Dishes.Stream (). Map (Dish :: getCalories) .Reduce (Inteiro :: Sum);
Embora esteja tudo bem, se você considerar a eficiência, ainda deve escolher o seguinte
int sum = disses.Stream (). MAPTONT (DISH :: getCalories) .sum ();
Escolha a melhor solução de acordo com a situação
Como mencionado acima, a programação funcional geralmente fornece várias maneiras de executar a mesma operação. O uso do coletor de coleta é mais complexo do que o uso de APIs de fluxo. A vantagem é que a coleta pode fornecer um nível mais alto de abstração e generalização e é mais fácil de reutilizar e personalizar.
Nosso conselho é explorar diferentes soluções para o problema em questão o máximo possível, sempre escolha a mais profissional, que geralmente é a melhor decisão em termos de legibilidade e desempenho.
Além de receber um valor inicial, a redução também pode usar o primeiro item como o valor inicial
Opcional <dish> mais -calloriedish = Dishes.Stream () .Collect (redução ((d1, d2) -> d1.getCalories ()> d2.getCalories ()? D1: d2));
Reduzindo
O uso da redução é bastante complicado, e o objetivo é mesclar dois valores em um valor.
public static <t, u> colector <t ,?, u> reduzindo (u identidade, função <? super t ,? estende u> mapeador, binário operador <u> op)
Primeiro, eu vi 3 genéricos.
U é o tipo de valor de retorno. Por exemplo, o calor calculado na demonstração acima, u é inteiro.
Em relação a T, t é o tipo de elemento no fluxo. A partir da função, podemos saber que a função do mapeador é receber um parâmetro t e depois retornar um resultado U. correspondente ao prato na demonstração.
? No meio da lista genérica com o coletor de valor de retorno, isso representa o tipo de contêiner. É claro que um colecionador precisa de um contêiner para armazenar dados. Aqui? Isso significa que o tipo de contêiner é incerto. De fato, o contêiner aqui é você [].
Sobre os parâmetros:
A identidade é o valor inicial do tipo de valor de retorno, que pode ser entendido como o ponto de partida do acumulador.
O Mapper é a função do mapa, e seu significado está na conversão de fluxos no fluxo de tipos que você deseja.
OP é a função principal, e sua função é como lidar com duas variáveis. Entre eles, a primeira variável é o valor cumulativo, que pode ser entendido como soma, e a segunda variável é o próximo elemento a ser calculado. Assim, o acúmulo é alcançado.
Há também um método sobrecarregado para omitir o primeiro parâmetro, o que significa que o primeiro parâmetro no fluxo é usado como o valor inicial.
public static <t> colector <t ,?, opcional <t>> Reduzindo (binário operador <T> op)
Vejamos a diferença entre o valor de retorno. T representa o valor de entrada e o tipo de valor de retorno, ou seja, o tipo de valor de entrada e o tipo de valor de saída são os mesmos. Outra diferença é opcional. Isso ocorre porque não há valor inicial e o primeiro parâmetro pode ser nulo. Quando o elemento de fluxo é nulo, é muito significativo retornar opcional.
Olhando para a lista de parâmetros, apenas o binário do operador é deixado. O BinaryOperator é uma interface de função tripla, o objetivo é calcular dois parâmetros do mesmo tipo e retorno dos valores do mesmo tipo. Pode ser entendido como 1> 2? 1: 2, isto é, encontre o valor máximo de dois números. Encontrar o valor máximo é uma declaração relativamente fácil de entender. Você pode personalizar a expressão lambda para selecionar o valor de retorno. Então, aqui, é para receber o tipo T de dois fluxos e retornar o valor de retorno do Tipo T. Também é bom usar a SUM para entender.
Na demonstração acima, verifica -se que as funções de redução e coleta são quase as mesmas, ambas retornam um resultado final. Por exemplo, podemos usar efeito de tolista:
// Implementar manualmente o TolistCollector --- abuso de regulamentos de redução e imutável --- Não é possível paralelo Lista <TEGER> Calorias = Dishes.Stream (). Map (Dish :: getCalories) .Reduce (novo ArrayList <Terger> (), (list <Tister> l, integger e)-> {L.add (e); L2) -> {l1.addall (L2);Deixe -me explicar as práticas acima.
<u> u reduz (u identidade, bifuncionamento <u ,? super t, u> acumulador, binário operador <u> combinador);
U é o tipo de valor de retorno, aqui está a lista
Bifunction <u ,? Super t, u> acumulador é um acumulador, e seu objetivo é acumular valores e regras de cálculo para elementos individuais. Aqui está a operação de lista e elementos e, finalmente, a lista de retorno. Isto é, adicione um elemento à lista.
O BinaryOperator <u> Combiner é um combinador, e o objetivo é mesclar duas variáveis dos tipos de valor de retorno em um. Aqui está a fusão de duas listas.
Existem dois problemas com esta solução: um é um problema semântico e o outro é um problema prático. O problema semântico é que o método de redução visa combinar dois valores para gerar um novo valor, que é uma redução imutável. Em vez disso, o design do método de coleta é alterar o contêiner e acumular os resultados a serem emitidos. Isso significa que o snippet de código acima está abusando do método de redução porque altera a lista como um acumulador no lugar. A semântica errada para usar o método de redução também cria um problema prático: essa redução não pode funcionar em paralelo, porque a modificação simultânea da mesma estrutura de dados por vários threads pode destruir a própria lista. Nesse caso, se você deseja a segurança do thread, precisará alocar uma nova lista por vez, e a alocação de objetos, por sua vez, afetará o desempenho. É por isso que a coleta é adequada para expressar reduções em recipientes mutáveis e, mais importante, é adequado para operações paralelas.
Resumo: Reduzir é adequado para redução imutável de contêineres, a coleta é adequada para redução de contêiner mutável. A coleta é adequada para o paralelismo.
Agrupamento
O banco de dados geralmente encontra a necessidade de soma em grupo e fornece o grupo por primitivo. Em Java, se você seguir o estilo instrucional (loops de gravação manualmente), será muito pesado e propenso a erros. O Java 8 fornece soluções funcionais.
Por exemplo, prato de grupo por tipo. Semelhante ao tomap anterior, mas o valor de agrupamento não é um prato, mas uma lista.
Mapa <tipo, list <dish>> DISHESBYTYPE = DISHES.STREAM (). Colete (Groupingby (Dish :: GetType));
aqui
public static <t, k> colector <t ,?, mapa <k, list <T>>
O classificador de parâmetro é uma função, projetada para receber um parâmetro e convertê -lo em outro tipo. A demonstração acima é converter o prato de elemento do fluxo em tipo de tipo e, em seguida, agrupar o fluxo de acordo com o tipo. Seu agrupamento interno é implementado através do hashmap. groupingby (classificador, hashmap :: novo, a jusante);
Além de agrupar de acordo com a função de propriedade do próprio elemento do fluxo, você também pode personalizar a base de agrupamento, como agrupamento de acordo com a faixa de calor.
Como você já sabe que o parâmetro de Groupingby é a função e o tipo de função do parâmetro é prato, você pode personalizar o classificador como:
Calorícula privada GetCaloriclevel (Dish d) {if (d.getCalories () <= 400) {retorna caloriclevel.diet; } else if (d.getCalories () <= 700) {return caloriclevel.normal; } else {return caloriclevel.fat; }}Basta passar nos parâmetros
Mapa <caloriclevel, list <dish>> DishesbyLevel = Dishes.Stream () .Collect (Groupingby (this :: getCaloriclevel));
Agrupamento multinível
Groupingby também sobrecarrega vários outros métodos, como
public static <t, k, a, d> colector <t ,?, mapa <k, d >> groupingby (função <? super t ,? estende k> classificador, colecionador <? super t, a, d> a jusante)
Existem muitos genéricos e horrores. Vamos ter um breve entendimento. Um classificador também é um classificador, que recebe o tipo de elemento do fluxo e retorna uma base para a qual você deseja agrupar, ou seja, a cardinalidade da base de agrupamento. Portanto, t representa o tipo de elemento atual do fluxo e k representa o tipo de elemento do agrupamento. O segundo parâmetro é a jusante e o jusante é um coletor de colecionador. Esse tipo de elemento coletor é uma subclasse de T, o contêiner do tipo contêiner é A e o tipo de valor de retorno de redução é D. isto é, o K do grupo é fornecido através do classificador e o valor do grupo é reduzido através do coletor do segundo parâmetro. Acontece que o código -fonte da demonstração anterior é:
public static <t, k> colector <t ,?, mapa <k, list <t>>> Groupingby (função <? super t ,? estende k> classificador) {retorna groupingby (classificador, tolist ()); }Use a tolista como o coletor de redução, e o resultado final é uma lista <Sish>; portanto, o tipo de valor do grupo termina é a lista <Sish>. Em seguida, o tipo de valor pode ser determinado analogamente pelo coletor de redução e há dezenas de milhões de coletores de redução. Por exemplo, quero agrupar o valor novamente, e o agrupamento também é uma espécie de redução.
// mapa de agrupamento de vários níveis <Tipo, mapa <caloriclevel, list <dish>>> bytypeandcalory = drishes.stream (). Colete (groupingby (drish :: gettype, groupingby (this :: getCaloriclevel)); System.out.println ("------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- System.out.println ("/t" + nível);Os resultados da verificação são:
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [Prato (nome = carne, vegetariano = falso, calorias = 700, Tipo = carne)] ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- tipo = outro)]]
Resumo: Os principais parâmetros do Groupingby são geradores K e V. O gerador V pode ser qualquer tipo de coletor de colecionador.
Por exemplo, o gerador V pode calcular o número, implementando assim a contagem selecionada (*) na instrução SQL da tabela A Grupo por tipo
Mapa <tipo, long> typeScount = Dishes.Stream (). Colete (Groupingby (Dish :: GetType, Counting ())); System.out.println (TypeScount); ---------- {peixe = 2, carne = 3, outros = 4} Grupo de pesquisa SQL mais alta pontuação select MAX(id) from table A group by Type
Mapa <tipo, opcional <dish>> MostCaloricByType = dAISS.STREAM () .Collect (Groupingby (Dish :: getType, maxby (comparador.comparingInt (Dish :: getCalories))));
Opcional aqui não faz sentido, porque certamente não é nulo. Então eu tive que retirá -lo. Usando colecionando e
Mapa <tipo, prato> maisCaloricbyType = dAISS.STREAM () .Collect (Groupingby (Dish :: getType, colecionando e (maxby (comparador.comparingInt (drish :: getCalories)), opcional :: get)));
Parece que o resultado está surgindo aqui, mas a ideia não concorda. Ele compila o alarme amarelo e muda para:
Mapa <tipo, prato> maisCaloricByType = dAISS.STREAM () .Collect (TOMAP (DISH :: getType, function.Identity (), binaryoperator.maxby (comparandont (Dish :: getCalories))));
Sim, o grupo se torna tomap, a chave ainda é do tipo, o valor ainda é o prato, mas há mais um parâmetro! ! Aqui respondemos ao poço no começo. A demonstração do tomap no começo é para uma compreensão fácil. Se for realmente usado, será morto. Sabemos que a reorganização de uma lista em um mapa inevitavelmente enfrentará o mesmo problema. Quando k é o mesmo, V substitui ou ignora? O método de demonstração anterior é inserir K novamente e lançar uma exceção diretamente quando K está presente:
java.lang.illegalStateException: prato de chave duplicado (nome = carne de porco, vegeta
A maneira correta é fornecer funções para lidar com conflitos. Nesta demonstração, o princípio de lidar com conflitos é encontrar o maior, que apenas atende a nossos requisitos para agrupar e encontrar o maior. (Eu realmente não quero mais fazer o aprendizado funcional do Java 8, sinto que há armadilhas de problemas de desempenho em todos os lugares)
Continue o mapeamento SQL do banco de dados, select sum(score) from table a group by Type
Mapa <tipo, número inteiro> TotalCaloriesByType = Dishes.Stream () .Collect (Groupingby (Dish :: GetType, SummingInt (Dish :: GetCalories)));
No entanto, outro colecionador que é frequentemente usado em conjunto com o Groupingby é gerado pelo método de mapeamento. Este método recebe dois parâmetros: uma função transforma elementos no fluxo e o outro coleta os objetos de resultado transformados. O objetivo é aplicar uma função de mapeamento a cada elemento de entrada antes da acumulação, para que o coletor que receba elementos de um tipo específico possa se adaptar a diferentes tipos de objetos. Deixe -me olhar para um exemplo prático de usar este colecionador. Por exemplo, você deseja obter quais calorículas estão lá no menu para cada tipo de prato. Podemos combinar o Groupingby e o mapeamento de colecionadores da seguinte forma:
Mapa <tipo, set <loriclevel>> CaloriclevelSbyType = disses.Stream () .Collect (Groupingby (Dish :: getType, mapeamento (this :: getCaloriclevel, toset ())));
O Toset aqui usa o hashset por padrão, e você também pode especificar manualmente a implementação específica tocollection (HashSet :: New)
Partição
O particionamento é um caso especial de agrupamento: um predicado (uma função que retorna um valor booleano) é usada como uma função de classificação, que é chamada de função de partição. A função de partição retorna um valor booleano, o que significa que o tipo de chave do mapa agrupado é booleano, para que possa ser dividido em até dois grupos: verdadeiro ou falso. Por exemplo, se você é vegetariano, convém separar o menu por vegetariano e não vegetariano:
Mapa <boolean, list <dish>> partitiotedMenu = Dishes.Stream (). Colete (Partitioningby (Dish :: Isvegegarian));
Obviamente, o uso do filtro pode alcançar o mesmo efeito:
Lista <Sish> vegetarianos = Dishes.Stream (). Filtro (Dish :: Isvegetarian) .Collect (colecionors.tolist ());
A vantagem do particionamento é salvar duas cópias, o que é útil quando você deseja classificar uma lista. Ao mesmo tempo, como o Groupingby, o Partioningby tem métodos sobrecarregados, que podem especificar o tipo de valor de agrupamento.
Mapa <booleano, mapa <tipo, list <dish>>> vegetarian -tishesbyType = dishes.Stream () .Collect (particionando (Dish :: isvegetarian, Groupingby (Dish :: getType)); mapa <boolean, inteiro> vegetariana Parttalcalories = DisheS.Stream (). SummingInt (Dish :: getCalories)))); mapa <boolean, prish> maisCaloricPartitionedByveGegarian = Dishes.Stream () .Collect (Partitioningby (Dish :: isvegegarian, colecionando e colecionando (maxby (comparando (Dish :: getCalories)), opcional: get);
Como o último exemplo de usar o colecionador Partioningby, colocamos o modelo de dados do menu de lado para ver um exemplo mais complexo e interessante: dividindo matrizes em números primários e não prime.
Primeiro, defina uma função de partição Prime:
private boolean isprime (int candidato) {int candidateroot = (int) math.sqrt ((duplo) candidato); retornar intstream.rangeClosed (2, Candidateroot) .noneMatch (i -> candidato % i == 0);}Em seguida, encontre os números primos e não primários de 1 a 100
Mapa <boolean, list <Integer>> partitionPrimes = intstream.rangeClosed (2, 100) .Boxed () .Collect (partitioningby (this :: isPrime));