¿Cuándo usarías collect()
vs reduce()
? ¿Alguien tiene buenos ejemplos concretos de cuándo definitivamente es mejor ir de una manera u otra?
Javadoc menciona que collect () es una reducción mutable .
Dado que es una reducción mutable, supongo que requiere sincronización (internamente) que, a su vez, puede ser perjudicial para el rendimiento. Presumiblemente, reduce()
es más fácilmente paralelizable a costa de tener que crear una nueva estructura de datos para el retorno después de cada paso en la reducción.
Sin embargo, las declaraciones anteriores son conjeturas y me encantaría que un experto interviniera aquí.
java
java-8
java-stream
jimhooker2002
fuente
fuente
Respuestas:
reduce
es una operación de " pliegue ", aplica un operador binario a cada elemento en la secuencia donde el primer argumento para el operador es el valor de retorno de la aplicación anterior y el segundo argumento es el elemento actual de la secuencia.collect
es una operación de agregación donde se crea una "colección" y cada elemento se "agrega" a esa colección. Las colecciones en diferentes partes de la secuencia se agregan juntas.El documento que vinculó da la razón de tener dos enfoques diferentes:
Entonces, el punto es que la paralelización es la misma en ambos casos, pero en el
reduce
caso de que apliquemos la función a los elementos de flujo en sí. En elcollect
caso, aplicamos la función a un contenedor mutable.fuente
int
es inmutable, por lo que no puede utilizar fácilmente una operación de recopilación. Podrías hacer un truco sucio como usar unaAtomicInteger
o algunas personalizadas,IntWrapper
pero ¿por qué lo harías? Una operación de plegado es simplemente diferente a una operación de recolección.reduce
método, donde puede devolver objetos de tipo diferente de los elementos de la secuencia.La razón es simplemente eso:
collect()
solo puede funcionar con objetos de resultado mutables .reduce()
está diseñado para trabajar con objetos de resultados inmutables .reduce()
ejemplo " con inmutable"collect()
ejemplo " con mutable"Por ejemplo, si desea calcular manualmente una suma con
collect()
ella, no puede funcionar,BigDecimal
sino solo conMutableInt
desde,org.apache.commons.lang.mutable
por ejemplo. Ver:Esto funciona porque no se supone que el acumulador
container.add(employee.getSalary().intValue());
devuelva un nuevo objeto con el resultado, sino que cambie el estadocontainer
del tipo mutableMutableInt
.Si desea utilizarlo
BigDecimal
en su lugar,container
no podría utilizar elcollect()
método, yacontainer.add(employee.getSalary());
que no cambiaría elcontainer
porqueBigDecimal
es inmutable. (Aparte de estoBigDecimal::new
no funcionaría ya queBigDecimal
no tiene un constructor vacío)fuente
Integer
constructor (new Integer(6)
), que está en desuso en versiones posteriores de Java.Integer.valueOf(6)
StringBuilder
cual es mutable. Ver: hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/…La reducción normal está destinada a combinar dos valores inmutables como int, double, etc. y producir uno nuevo; Es una reducción inmutable . En contraste, el método de recolección está diseñado para mutar un contenedor para acumular el resultado que se supone que debe producir.
Para ilustrar el problema, supongamos que desea lograrlo
Collectors.toList()
usando una reducción simple comoEste es el equivalente de
Collectors.toList()
. Sin embargo, en este caso mutas elList<Integer>
. Como sabemosArrayList
, no es seguro para subprocesos, ni es seguro agregar / eliminar valores mientras itera, por lo que obtendrá una excepción concurrenteArrayIndexOutOfBoundsException
o cualquier tipo de excepción (especialmente cuando se ejecuta en paralelo) cuando actualiza la lista o el combinador intenta fusionar las listas porque está mutando la lista acumulando (agregando) los enteros. Si desea hacer que este hilo sea seguro, debe pasar una nueva lista cada vez que perjudicaría el rendimiento.En contraste, las
Collectors.toList()
obras de manera similar. Sin embargo, garantiza la seguridad del hilo cuando acumula los valores en la lista. De la documentación para elcollect
método :Entonces para responder a su pregunta:
si tiene valores inmutables, tales como
ints
,doubles
,Strings
a continuación, la reducción de lo normal funciona bien. Sin embargo, si tiene que decirreduce
sus valores para decir unaList
(estructura de datos mutable), entonces necesita usar la reducción mutable con elcollect
método.fuente
x
hilos, cada uno "añadiendo a la identidad" y luego combinándose. Buen ejemplo.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); } }
intenté y no obtuve la excepciónDeje que la corriente sea a <- b <- c <- d
En reducción,
usted tendrá ((a # b) # c) # d
donde # es esa operación interesante que te gustaría hacer.
En colección,
su recolector tendrá algún tipo de estructura de recolección K.
K consume a. K entonces consume b. K entonces consume c. K entonces consume d.
Al final, le preguntas a K cuál es el resultado final.
K entonces te lo da.
fuente
Son muy diferentes en la huella de memoria potencial durante el tiempo de ejecución. Mientras
collect()
recopila y coloca todos los datos en la colección,reduce()
le pide explícitamente que especifique cómo reducir los datos que llegaron a través de la transmisión.Por ejemplo, si desea leer algunos datos de un archivo, procesarlos y colocarlos en alguna base de datos, puede terminar con un código de flujo de Java similar a este:
En este caso, usamos
collect()
para forzar a Java a transmitir datos y hacer que guarde el resultado en la base de datos. Sincollect()
los datos nunca se lee y nunca se almacena.Este código genera felizmente un
java.lang.OutOfMemoryError: Java heap space
error de tiempo de ejecución, si el tamaño del archivo es lo suficientemente grande o el tamaño del montón es lo suficientemente bajo. La razón obvia es que intenta apilar todos los datos que llegaron a través de la secuencia (y, de hecho, ya se han almacenado en la base de datos) en la colección resultante y esto explota el montón.Sin embargo, si reemplaza
collect()
conreduce()
, ya no será un problema, ya que este último reducirá y descartará todos los datos que lo lograron.En el ejemplo presentado, simplemente reemplace
collect()
con algo conreduce
:Ni siquiera necesita preocuparse de hacer que el cálculo dependa de,
result
ya que Java no es un lenguaje de FP (programación funcional) puro y no puede optimizar los datos que no se utilizan en la parte inferior de la secuencia debido a los posibles efectos secundarios .fuente
System.out.println (suma);
La función de reducción maneja dos parámetros, el primer parámetro es el valor de retorno anterior en el flujo, el segundo parámetro es el valor de cálculo actual en el flujo, suma el primer valor y el valor actual como el primer valor en el próximo cálculo.
fuente
De acuerdo con los documentos
Básicamente,
reducing()
solo lo usarías cuando te veas forzado a recoger. Aquí hay otro ejemplo :De acuerdo con este tutorial, reducir es a veces menos eficiente
Por lo tanto, la identidad se "reutiliza" en un escenario reducido, por lo que es un poco más eficiente
.reduce
si es posible.fuente