que - Propósito del tercer argumento para ''reducir'' la función en la programación funcional de Java 8
programacion funcional java (3)
¿Bajo qué circunstancias es el tercer argumento para ''reducir'' llamado en Java 8 streams?
El siguiente código intenta recorrer una lista de cadenas y sumar los valores del punto de código del primer carácter de cada una. El valor devuelto por el lambda final nunca parece usarse y, si inserta un println, nunca parece ser invocado. La documentación lo describe como un "combinador", pero no puedo encontrar más detalles ...
int result =
data.stream().reduce(0, (total,s) -> total + s.codePointAt(0), (a,b) -> 1000000);
¿Estás hablando de esta función ?
reduce <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
Realiza una reducción en los elementos de esta secuencia, utilizando las funciones de identidad, acumulación y combinación proporcionadas. Esto es equivalente a:
U result = identity; for (T element : this stream) result = accumulator.apply(result, element) return result;
pero no está obligado a ejecutar secuencialmente. El valor de identidad debe ser una identidad para la función del combinador. Esto significa que para todos ustedes, combinador (identidad, u) es igual a u. Además, la función del combinador debe ser compatible con la función del acumulador; para todos ustedes, debe tener lo siguiente:
combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
Esta es una operación de terminal.
Nota de la API: muchas reducciones que usan este formulario se pueden representar más simplemente mediante una combinación explícita de operaciones de mapa y reducción. La función de acumulador actúa como un mapeador y acumulador fusionado, que a veces puede ser más eficiente que el mapeo y la reducción por separado, como cuando conocer el valor previamente reducido le permite evitar algunos cálculos. Tipo Parámetros: U - El tipo del resultado Parámetros: identidad - el valor de identidad para el acumulador de función combinador - una función asociativa, no interferente, sin estado para incorporar un elemento adicional en un combinador de resultados - un asociativo, no interferente, apátrida función para combinar dos valores, que deben ser compatibles con la función acumuladora. Devuelve: el resultado de la reducción. Consulte también: reducir (operador binario), reducir (objeto, operador binario)
Supongo que su propósito es permitir el cálculo paralelo, por lo que supongo que solo se usa si la reducción se realiza en paralelo. Si se realiza de forma secuencial, no es necesario usar combiner
. No lo sé con certeza, solo estoy adivinando en base al comentario del doc "[...] no está obligado a ejecutar secuencialmente" y las muchas otras menciones de "ejecución paralela" en los comentarios.
Código de prueba simple para confirmar el uso del combinador:
String[] strArray = {"abc", "mno", "xyz"};
List<String> strList = Arrays.asList(strArray);
System.out.println("stream test");
int streamResult = strList.stream().reduce(
0,
(total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); },
(a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "]"); return 1000000;}
);
System.out.println("streamResult: " + streamResult);
System.out.println("parallelStream test");
int parallelStreamResult = strList.parallelStream().reduce(
0,
(total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); },
(a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "]"); return 1000000;}
);
System.out.println("parallelStreamResult: " + parallelStreamResult);
System.out.println("parallelStream test2");
int parallelStreamResult2 = strList.parallelStream().reduce(
0,
(total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); },
(a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "] a+b[" + (a+b) + "]"); return a+b;}
);
System.out.println("parallelStreamResult2: " + parallelStreamResult2);
Salida:
stream test
accumulator: total[0] s[abc] s.codePointAt(0)[97]
accumulator: total[97] s[mno] s.codePointAt(0)[109]
accumulator: total[206] s[xyz] s.codePointAt(0)[120]
streamResult: 326
parallelStream test
accumulator: total[0] s[mno] s.codePointAt(0)[109]
accumulator: total[0] s[abc] s.codePointAt(0)[97]
accumulator: total[0] s[xyz] s.codePointAt(0)[120]
combiner: a[109] b[120]
combiner: a[97] b[1000000]
parallelStreamResult: 1000000
parallelStream test2
accumulator: total[0] s[mno] s.codePointAt(0)[109]
accumulator: total[0] s[xyz] s.codePointAt(0)[120]
accumulator: total[0] s[abc] s.codePointAt(0)[97]
combiner: a[109] b[120] a+b[229]
combiner: a[97] b[229] a+b[326]
parallelStreamResult2: 326
Creo que el párrafo de las operaciones de reducción del resumen del paquete java.util.stream
puede responder la pregunta. Permítanme citar la parte más importante aquí:
En su forma más general, una operación de reducción en elementos de tipo <T>
arrojan un resultado de tipo <U>
requiere tres parámetros:
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
Aquí, el elemento de identidad es a la vez un valor semilla inicial para la reducción y un resultado predeterminado si no hay elementos de entrada. La función del acumulador toma un resultado parcial y el siguiente elemento, y produce un nuevo resultado parcial. La función del combinador combina dos resultados parciales para producir un nuevo resultado parcial. (El combinador es necesario en reducciones paralelas, donde la entrada se divide, una acumulación parcial calculada para cada partición, y luego los resultados parciales se combinan para producir un resultado final). Más formalmente, el valor de identidad debe ser una identidad para el combinador función. Esto significa que para todos u
, combiner.apply(identity, u)
es igual a u
. Además, la función del combinador debe ser asociativa y debe ser compatible con la función del acumulador: para todos u
, combiner.apply(u, accumulator.apply(identity, t))
debe ser equals()
a accumulator.apply(u, t)
.
La forma de tres argumentos es una generalización de la forma de dos argumentos, que incorpora un paso de mapeo en el paso de acumulación. Podríamos volver a emitir el ejemplo simple de suma de pesos usando la forma más general de la siguiente manera:
int sumOfWeights = widgets.stream()
.reduce(0,
(sum, b) -> sum + b.getWeight())
Integer::sum);
aunque la forma explícita de reducir mapas es más legible y, por lo tanto, generalmente debería preferirse. La forma generalizada se proporciona para casos en los que se puede optimizar el trabajo significativo combinando el mapeo y la reducción en una sola función.
En otras palabras, por lo que yo entiendo, la forma de tres argumentos es útil en dos casos:
- Cuando la ejecución paralela importa.
- Cuando se puede lograr una optimización del rendimiento significativa combinando los pasos de mapeo y acumulación. De lo contrario, se puede usar un formulario de reducción de mapa explícito más simple y legible.
La forma explícita se menciona previamente en el mismo documento:
int sumOfWeights = widgets.parallelStream()
.filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();