Quando você usaria collect()
vs reduce()
? Alguém tem exemplos bons e concretos de quando é definitivamente melhor seguir um caminho ou outro?
Javadoc menciona que collect () é uma redução mutável .
Dado que é uma redução mutável, presumo que exija sincronização (internamente), o que, por sua vez, pode ser prejudicial ao desempenho. Presumivelmente, reduce()
é mais fácil paralelamente ao custo de ter que criar uma nova estrutura de dados para retorno após cada etapa da redução.
As declarações acima são suposições, no entanto, e eu adoraria que um especialista falasse aqui.
java
java-8
java-stream
jimhooker2002
fonte
fonte
Respostas:
reduce
é uma operação " fold ", aplica um operador binário a cada elemento no fluxo, em que o primeiro argumento para o operador é o valor de retorno do aplicativo anterior e o segundo argumento é o elemento de fluxo atual.collect
é uma operação de agregação em que uma "coleção" é criada e cada elemento é "adicionado" a essa coleção. Coleções em diferentes partes do fluxo são adicionadas.O documento que você vinculou fornece o motivo de ter duas abordagens diferentes:
Portanto, o ponto é que a paralelização é a mesma nos dois casos, mas, no
reduce
caso, aplicamos a função aos próprios elementos do fluxo. Nocollect
caso, aplicamos a função a um contêiner mutável.fonte
int
é imutável, portanto você não pode usar prontamente uma operação de coleta. Você poderia fazer um truque sujo como usar umAtomicInteger
ou algum costume,IntWrapper
mas por que faria? Uma operação de dobra é simplesmente diferente de uma operação de coleta.reduce
método, no qual você pode retornar objetos do tipo diferentes dos elementos do fluxo.A razão é simplesmente que:
collect()
só pode trabalhar com objetos de resultado mutáveis .reduce()
foi projetado para trabalhar com objetos de resultado imutáveis .exemplo "
reduce()
com imutável"exemplo "
collect()
com mutável"Por exemplo, se você deseja calcular manualmente uma soma usando-a,
collect()
ela não pode funcionar,BigDecimal
mas apenas comMutableInt
from,org.apache.commons.lang.mutable
por exemplo. Vejo:Isso funciona porque o acumulador
container.add(employee.getSalary().intValue());
não deve retornar um novo objeto com o resultado, mas alterar o estado do mutávelcontainer
do tipoMutableInt
.Se você gostaria de usar
BigDecimal
, em vez docontainer
que você não poderia usar ocollect()
método quecontainer.add(employee.getSalary());
não mudaria ocontainer
porqueBigDecimal
ele é imutável. (Além dissoBigDecimal::new
, não funcionaria, poisBigDecimal
não possui construtor vazio)fonte
Integer
construtor (new Integer(6)
), que foi descontinuado em versões posteriores do Java.Integer.valueOf(6)
StringBuilder
que é mutável. Veja: hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/…A redução normal visa combinar dois valores imutáveis , como int, double, etc. e produzir um novo; é uma redução imutável . Por outro lado, o método de coleta é projetado para alterar um contêiner para acumular o resultado que ele deveria produzir.
Para ilustrar o problema, suponhamos que você queira obter
Collectors.toList()
uma redução simples comoIsso é equivalente a
Collectors.toList()
. No entanto, neste caso, você modifica oList<Integer>
. Como sabemos queArrayList
não é seguro para threads, nem é seguro adicionar / remover valores dele durante a iteração, para que você receba uma exceção simultâneaArrayIndexOutOfBoundsException
ou qualquer outro tipo de exceção (especialmente quando executado em paralelo) ao atualizar a lista ou o combinador tenta mesclar as listas porque você está mudando a lista acumulando (adicionando) os números inteiros a ela. Se você deseja tornar esse thread seguro, precisará passar uma nova lista sempre que prejudicar o desempenho.Por outro lado, os
Collectors.toList()
trabalhos de maneira semelhante. No entanto, garante a segurança do encadeamento quando você acumula os valores na lista. Na documentação docollect
método :Então, para responder sua pergunta:
se você tiver valores imutáveis, como
ints
,doubles
,Strings
redução que o normal funciona muito bem. No entanto, se você tiver quereduce
digitar seus valores aList
(estrutura de dados mutáveis), precisará usar a redução mutável com ocollect
métodofonte
x
threads, cada um "adicionando à identidade" e depois combinando. Bom exemplo.public static void main(String[] args) { List<Integer> l = new ArrayList<>(); l.add(1); l.add(10); l.add(3); l.add(-3); l.add(-4); List<Integer> numbers = l.stream().reduce( new ArrayList<Integer>(), (List<Integer> l2, Integer e) -> { l2.add(e); return l2; }, (List<Integer> l1, List<Integer> l2) -> { l1.addAll(l2); return l1; });for(Integer i:numbers)System.out.println(i); } }
Eu tentei e não obter CCM exceçãoSeja o fluxo a <- b <- c <- d
Em redução,
você terá ((a # b) # c) # d
onde # é aquela operação interessante que você gostaria de fazer.
Na coleção,
seu colecionador terá algum tipo de estrutura de coleta K.
K consome a. K então consome b. K então consome c. K então consome d.
No final, você pergunta a K qual é o resultado final.
K então dá a você.
fonte
Eles são muito diferentes no espaço potencial de memória durante o tempo de execução. Enquanto
collect()
coleta e coloca todos os dados na coleção,reduce()
solicita explicitamente que você especifique como reduzir os dados que passaram pelo fluxo.Por exemplo, se você quiser ler alguns dados de um arquivo, processá-lo e colocá-lo em algum banco de dados, poderá acabar com um código de fluxo java semelhante a este:
Nesse caso, usamos
collect()
para forçar o java a transmitir dados e salvar o resultado no banco de dados. Semcollect()
os dados nunca é lido e nunca é armazenado.Felizmente, esse código gera um
java.lang.OutOfMemoryError: Java heap space
erro de tempo de execução, se o tamanho do arquivo for grande o suficiente ou o tamanho da pilha for baixo o suficiente. O motivo óbvio é que ele tenta empilhar todos os dados que passaram pelo fluxo (e, de fato, já foram armazenados no banco de dados) na coleção resultante e isso acaba com a pilha.No entanto, se você substituir
collect()
porreduce()
- não será mais um problema, pois o último reduzirá e descartará todos os dados que o fizeram.No exemplo apresentado, basta substituir
collect()
por algo comreduce
:Você não precisa nem se preocupar em fazer o cálculo depender,
result
pois o Java não é uma linguagem pura de FP (programação funcional) e não pode otimizar os dados que não estão sendo usados na parte inferior do fluxo devido aos possíveis efeitos colaterais .fonte
System.out.println (sum);
A função Reduce manipula dois parâmetros, o primeiro parâmetro é o valor de retorno anterior no fluxo, o segundo parâmetro é o valor de cálculo atual no fluxo, soma o primeiro valor e o valor atual como o primeiro valor na próxima caculação.
fonte
De acordo com os documentos
Então, basicamente, você usaria
reducing()
apenas quando forçado dentro de uma coleção. Aqui está outro exemplo :De acordo com este tutorial, reduzir às vezes é menos eficiente
Portanto, a identidade é "reutilizada" em um cenário de redução, sendo um pouco mais eficiente
.reduce
se possível.fonte