versiones descargar anteriores actualizar java java-8

descargar - Java 8 Streams: recopilar vs reducir



java versiones anteriores (7)

Aquí está el ejemplo del código

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7); int sum = list.stream().reduce((x,y) -> { System.out.println(String.format("x=%d,y=%d",x,y)); return (x + y); }).get();

System.out.println (sum);

Aquí está el resultado de ejecución:

x=1,y=2 x=3,y=3 x=6,y=4 x=10,y=5 x=15,y=6 x=21,y=7 28

Reduzca la función maneje dos parámetros, el primer parámetro es el valor de retorno anterior int la secuencia, el segundo parámetro es el valor de cálculo actual en la secuencia, suma el primer valor y el valor actual como el primer valor en el siguiente cálculo.

¿Cuándo usarías collect() vs reduce() ? ¿Alguien tiene ejemplos buenos y concretos de cuándo definitivamente es mejor ir de una manera u otra?

Javadoc menciona que collect () es una reducción mutable .

Dado que se trata de una reducción mutable, supongo que requiere sincronización (interna) que, a su vez, puede ser perjudicial para el rendimiento. Es de suponer que 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.

Las declaraciones anteriores son conjeturas sin embargo y me encantaría que un experto presente aquí.


De acuerdo con los documentos

Los colectores reductores () son más útiles cuando se usan en una reducción de niveles múltiples, en sentido descendente de groupingBy o partitioningBy. Para realizar una reducción simple en una secuencia, use Stream.reduce (BinaryOperator) en su lugar.

Entonces, básicamente, usarías reducing() solo cuando se forza dentro de un collect. Aquí hay otro example :

For example, given a stream of Person, to calculate the longest last name of residents in each city: Comparator<String> byLength = Comparator.comparing(String::length); Map<String, String> longestLastNameByCity = personList.stream().collect(groupingBy(Person::getCity, reducing("", Person::getLastName, BinaryOperator.maxBy(byLength))));

De acuerdo con este tutorial, reducir es a veces menos eficiente

La operación de reducción siempre devuelve un nuevo valor. Sin embargo, la función de acumulador también devuelve un nuevo valor cada vez que procesa un elemento de una secuencia. Supongamos que desea reducir los elementos de una secuencia a un objeto más complejo, como una colección. Esto podría obstaculizar el rendimiento de su aplicación. Si su operación de reducción implica agregar elementos a una colección, cada vez que su función de acumulador procesa un elemento, crea una nueva colección que incluye el elemento, que es ineficiente. Sería más eficiente para usted actualizar una colección existente en su lugar. Puede hacer esto con el método Stream.collect, que la siguiente sección describe ...

Entonces, la identidad se "reutiliza" en un escenario de reducción, por lo que es un poco más eficiente ir con .reduce si es posible.


Deje que la transmisión sea un <- b <- c <- d

En reducción,

Tendrás ((a # b) # c) # d

donde # es esa operación interesante que le gustaría hacer.

En la colección,

su coleccionista tendrá algún tipo de estructura de recolección K.

K consume a. K luego consume b. K luego consume c. K luego consume d.

Al final, le preguntas a K cuál es el resultado final.

K luego te lo da.


La razón es simplemente eso:

  • collect() solo puede funcionar con objetos de resultados mutables .
  • reduce() está diseñado para trabajar con objetos resultantes inmutables .

Ejemplo de " reduce() con inmutable"

public class Employee { private Integer salary; public Employee(String aSalary){ this.salary = new Integer(aSalary); } public Integer getSalary(){ return this.salary; } } @Test public void testReduceWithImmutable(){ List<Employee> list = new LinkedList<>(); list.add(new Employee("1")); list.add(new Employee("2")); list.add(new Employee("3")); Integer sum = list .stream() .map(Employee::getSalary) .reduce(0, (Integer a, Integer b) -> Integer.sum(a, b)); assertEquals(new Integer(6), sum); }

Ejemplo " collect() con mutable"

Por ejemplo, si desea calcular manualmente una suma utilizando collect() no puede funcionar con BigDecimal pero solo con MutableInt desde org.apache.commons.lang.mutable por ejemplo. Ver:

public class Employee { private MutableInt salary; public Employee(String aSalary){ this.salary = new MutableInt(aSalary); } public MutableInt getSalary(){ return this.salary; } } @Test public void testCollectWithMutable(){ List<Employee> list = new LinkedList<>(); list.add(new Employee("1")); list.add(new Employee("2")); MutableInt sum = list.stream().collect( MutableInt::new, (MutableInt container, Employee employee) -> container.add(employee.getSalary().intValue()) , MutableInt::add); assertEquals(new MutableInt(3), sum); }

Esto funciona porque el accumulator container.add(employee.getSalary().intValue()); no debe devolver un nuevo objeto con el resultado sino cambiar el estado del container mutable de tipo MutableInt .

Si desea utilizar BigDecimal en BigDecimal lugar para el container no puede usar el método collect() como container.add(employee.getSalary()); no cambiaría el container porque BigDecimal es inmutable. (Aparte de esto, BigDecimal::new no funcionaría ya que BigDecimal no tiene un constructor vacío)


La reducción normal está destinada a combinar dos valores inmutables , como int, double, etc. y producir uno nuevo; es una reducción inmutable . Por el contrario, el método de recolección está diseñado para mutar un contenedor y acumular el resultado que se supone que produce.

Para ilustrar el problema, supongamos que quiere lograr Collectors.toList() usando una reducción simple como la siguiente

List<Integer> numbers = stream.reduce( new ArrayList<Integer>(), (List<Integer> l, Integer e) -> { l.add(e); return l; }, (List<Integer> l1, List<Integer> l2) -> { l1.addAll(l2); return l1; });

Este es el equivalente de Collectors.toList() . Sin embargo, en este caso, mute la List<Integer> . Como sabemos, ArrayList no es seguro para subprocesos, ni es seguro agregar / eliminar valores al iterar, por lo que obtendrá una excepción concurrente o una excepción arrayIndexOutBound o cualquier tipo de excepción (especialmente cuando se ejecuta en paralelo) cuando actualice la lista o el combinador intenta combinar las listas porque está mutando la lista al acumular (sumar) los enteros. Si desea que este subproceso sea seguro, debe pasar una nueva lista cada vez, lo que perjudicaría el rendimiento.

Por el contrario, Collectors.toList() funciona de manera similar. Sin embargo, garantiza la seguridad del hilo cuando se acumulan los valores en la lista. De la documentación para el método de collect :

Realiza una operación de reducción mutable en los elementos de esta secuencia usando un Colector. Si la secuencia es paralela y el recopilador es concurrente, y la secuencia no está ordenada o el recopilador está desordenado, se realizará una reducción simultánea. Cuando se ejecutan en paralelo, múltiples resultados intermedios pueden ser instanciados, poblados y fusionados para mantener el aislamiento de estructuras de datos mutables. Por lo tanto, incluso cuando se ejecuta en paralelo con estructuras de datos que no son seguras para subprocesos (como ArrayList), no se necesita sincronización adicional para una reducción paralela. link

Entonces para responder a tu pregunta:

¿Cuándo usarías collect() vs reduce() ?

si tienes valores inmutables como ints , doubles , Strings , la reducción normal funciona bien. Sin embargo, si tiene que reduce sus valores en decir una List (estructura de datos mutable), entonces necesita usar la reducción mutable con el método de collect .


Primero, los valores de retorno son diferentes:

<R,A> R collect(Collector<? super T,A,R> collector) T reduce(T identity, BinaryOperator<T> accumulator)

Entonces collect devuelve cualquier R mientras que reduce devuelve T - el tipo de Stream .

reduce es una operación de " fold ", 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 de flujo actual.

collection 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 para tener dos enfoques diferentes:

Si quisiéramos tomar una secuencia de cadenas y concatenarlas en una sola cadena larga, podríamos lograr esto con una reducción ordinaria:

String concatenated = strings.reduce("", String::concat)

Obtendríamos el resultado deseado e incluso funcionaría en paralelo. Sin embargo, puede que no estemos contentos con el rendimiento. Tal implementación haría una gran cantidad de copia de cadenas, y el tiempo de ejecución sería O (n ^ 2) en el número de caracteres. Un enfoque más eficaz sería acumular los resultados en un StringBuilder, que es un contenedor mutable para la acumulación de cadenas. Podemos usar la misma técnica para paralelizar la reducción mutable como lo hacemos con la reducción ordinaria.

Entonces, el punto es que la paralelización es la misma en ambos casos, pero en el caso de reduce aplicamos la función a los elementos de la secuencia en sí mismos. En el caso de collect , aplicamos la función a un contenedor mutable.


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() explícitamente le pide que especifique cómo reducir los datos que lo hicieron a través de la secuencia.

Por ejemplo, si desea leer algunos datos de un archivo, procesarlos y ponerlos en alguna base de datos, puede terminar con un código de secuencia de Java similar a este:

streamDataFromFile(file) .map(data -> processData(data)) .map(result -> database.save(result)) .collect(Collectors.toList());

En este caso, usamos collect() para forzar a java a transmitir datos y hacer que guarde el resultado en la base de datos. Sin collect() los datos nunca se leen y nunca se almacenan.

Este código felizmente genera un java.lang.OutOfMemoryError: Java heap space error de tiempo de ejecución del java.lang.OutOfMemoryError: Java heap space , si el tamaño del archivo es lo suficientemente grande o el tamaño del almacenamiento dinámico es lo suficientemente bajo. La razón obvia es que intenta apilar todos los datos que lo hicieron 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 hace explotar el montón.

Sin embargo, si reemplaza collect() con reduce() - ya no será un problema ya que este último reducirá y descartará todos los datos que lo hicieron.

En el ejemplo presentado, simplemente reemplace collect() con algo con reduce :

.reduce(0L, (aLong, result) -> aLong, (aLong1, aLong2) -> aLong1);

No es necesario ni siquiera preocuparse de que el cálculo dependa del result ya que Java no es un lenguaje FP puro (programación funcional) y no puede optimizar los datos que no se utilizan en la parte inferior de la secuencia debido a la posible lateralidad. efectos.