Anotación de traducción: mapear (mapear) y reducir (reducir, simplificar) son dos conceptos muy básicos en matemáticas. Han aparecido en varios lenguajes de programación funcionales durante mucho tiempo. No fue hasta 2003 que Google los impulsó. los aplicó a Después de que se implementó la computación paralela en sistemas distribuidos, el nombre de esta combinación comenzó a brillar en el mundo de la informática (esos fanáticos funcionales pueden no pensarlo). En este artículo, veremos el debut de las combinaciones map y reduce después de que Java 8 admita la programación funcional (esto es solo una introducción preliminar, habrá temas especiales sobre ellos más adelante).
Reducir un conjunto
Hasta ahora hemos introducido varias técnicas nuevas para operar colecciones: encontrar elementos coincidentes, encontrar elementos individuales y transformaciones de colecciones. Estas operaciones tienen una cosa en común: todas operan sobre un único elemento de la colección. No es necesario comparar elementos ni realizar operaciones en dos elementos. En esta sección veremos cómo comparar elementos y mantener dinámicamente el resultado de una operación durante el recorrido de la colección.
Comencemos con ejemplos simples y luego avancemos. En el primer ejemplo, primero recorremos la colección de amigos y calculamos el número total de caracteres en todos los nombres.
Copie el código de código de la siguiente manera:
System.out.println("Número total de caracteres en todos los nombres: " + friends.stream()
.mapToInt(nombre -> nombre.longitud())
.suma());
Para calcular el número total de caracteres necesitamos saber la longitud de cada nombre. Esto se puede lograr fácilmente mediante el método mapToInt(). Una vez que hayamos convertido los nombres en las longitudes correspondientes, solo necesitamos sumarlos al final. Tenemos un método sum() incorporado para lograr esto. Aquí está el resultado final:
Copie el código de código de la siguiente manera:
Número total de caracteres en todos los nombres: 26
Usamos una variante de la operación de mapa, el método mapToInt() (como mapToInt, mapToDouble, etc., que generará tipos específicos de flujos, como IntStream, DoubleStream), y luego calculamos el número total de caracteres en función del longitud devuelta.
Además de utilizar el método de suma, existen muchos métodos similares que se pueden utilizar, como max() para encontrar la longitud máxima, min() para encontrar la longitud mínima, sorted() para ordenar las longitudes, Average() para encuentre la longitud promedio, etc. espere.
Otro aspecto atractivo del ejemplo anterior es el cada vez más popular modo MapReduce. El método map() realiza el mapeo, y el método sum() es una operación de reducción de uso común. De hecho, la implementación del método sum() en el JDK utiliza el método reduce(). Echemos un vistazo a algunas de las formas más utilizadas de operaciones de reducción.
Por ejemplo, recorremos todos los nombres e imprimimos el que tiene el nombre más largo. Si hay varios nombres más largos, imprimimos el que encontramos primero. Una forma es calcular la longitud máxima y luego seleccionar el primer elemento que coincida con esta longitud. Sin embargo, hacer esto requiere recorrer la lista dos veces, lo cual es demasiado ineficiente. Aquí es donde entra en juego la operación de reducción.
Podemos usar la operación de reducción para comparar las longitudes de dos elementos, luego devolver el más largo y compararlo con los elementos restantes. Al igual que otras funciones de orden superior que vimos antes, el método reduce() también atraviesa toda la colección. Entre otras cosas, registra el resultado del cálculo devuelto por la expresión lambda. Si hay un ejemplo que pueda ayudarnos a comprender esto mejor, veamos primero un fragmento de código.
Copie el código de código de la siguiente manera:
final Opcional<String> aLongName = friends.stream()
.reduce((nombre1, nombre2) ->
nombre1.longitud() >= nombre2.longitud() ? nombre1: nombre2);
aLongName.ifPresent(nombre ->
System.out.println(String.format("El nombre más largo: %s", nombre)));
La expresión lambda pasada al método reduce() recibe dos parámetros, nombre1 y nombre2, compara sus longitudes y devuelve el más largo. El método reduce() no tiene idea de lo que vamos a hacer. Esta lógica se elimina en la expresión lambda que pasamos; esta es una implementación ligera del patrón Estrategia.
Esta expresión lambda se puede adaptar al método apply de la interfaz funcional de un BinaryOperator en el JDK. Este es exactamente el tipo de argumento que acepta el método reduce. Ejecutemos este método de reducción y veamos si puede seleccionar correctamente el primero de los dos nombres más largos.
Copie el código de código de la siguiente manera:
Un nombre más largo: Brian
Cuando el método reduce() atraviesa la colección, primero llama a la expresión lambda en los dos primeros elementos de la colección y el resultado devuelto por la llamada continúa utilizándose para la siguiente llamada. En la segunda llamada, el valor de nombre1 está vinculado al resultado de la llamada anterior y el valor de nombre2 es el tercer elemento de la colección. Los elementos restantes también se denominan en este orden. El resultado de la última llamada a la expresión lambda es el resultado devuelto por todo el método reduce().
El método reduce() devuelve un valor opcional porque la colección que se le pasa puede estar vacía. En ese caso, no habría ningún nombre más largo. Si la lista tiene solo un elemento, el método reduce devuelve directamente ese elemento y no llama a la expresión lambda.
De este ejemplo podemos inferir que el resultado de reducir solo puede ser como máximo un elemento del conjunto. Si deseamos devolver un valor predeterminado o base, podemos usar una variante del método reduce() que acepta un parámetro adicional. Por ejemplo, si el nombre más corto es Steve, podemos pasarlo al método reduce() así:
Copie el código de código de la siguiente manera:
Cadena final steveOrLonger = amigos.stream()
.reduce("Steve", (nombre1, nombre2) ->
nombre1.longitud() >= nombre2.longitud() ? nombre1: nombre2);
Si hay un nombre más largo que él, se seleccionará este nombre; de lo contrario, se devolverá el valor base Steve. Esta versión del método reduce() no devuelve un objeto Opcional, porque si la colección está vacía, se devolverá un valor predeterminado independientemente del caso en el que no haya ningún valor de retorno;
Antes de terminar este capítulo, echemos un vistazo a una operación muy básica pero no tan fácil en operaciones de conjuntos: fusionar elementos.
Fusionar elementos
Hemos aprendido a encontrar elementos, recorrer y convertir colecciones. Sin embargo, hay otra operación común: empalmar elementos de colección. Sin esta función join() recién agregada, el código conciso y elegante mencionado anteriormente sería en vano. Este método simple es tan práctico que se ha convertido en una de las funciones más utilizadas en el JDK. Veamos cómo usarlo para imprimir elementos de una lista, separados por comas.
Todavía usamos esta lista de amigos. Si utiliza el método antiguo en la biblioteca JDK, ¿qué debe hacer si desea imprimir todos los nombres separados por comas?
Tenemos que recorrer la lista e imprimir los elementos uno por uno. El bucle for en Java 5 ha sido mejorado con respecto al anterior, así que usémoslo.
Copie el código de código de la siguiente manera:
para (nombre de cadena: amigos) {
System.out.print(nombre + ", ");
}
Sistema.out.println();
El código es muy simple, veamos cuál es su resultado.
Copie el código de código de la siguiente manera:
Brian, Nate, Neal, Raju, Sara, Scott,
Maldita sea, hay esa molesta coma al final (¿podemos culpar a Scott al final?). ¿Cómo puedo decirle a Java que no ponga una coma aquí? Desafortunadamente, el bucle se ejecuta paso a paso y no es fácil hacer algo especial al final. Para resolver este problema, podemos utilizar el método del bucle original.
Copie el código de código de la siguiente manera:
for(int i = 0; i < amigos.tamaño() - 1; i++) {
System.out.print(amigos.get(i) + ", ");
}
si(amigos.tamaño() > 0)
System.out.println(amigos.get(amigos.tamaño() - 1));
Veamos si el resultado de esta versión es correcto.
Copie el código de código de la siguiente manera:
Brian, Nate, Neal, Raju, Sara, Scott
El resultado sigue siendo bueno, pero este código no favorece. Sálvanos, Java.
Ya no tenemos que soportar este dolor. La clase StringJoiner en Java 8 nos ayuda a resolver estos problemas. No solo eso, la clase String también agrega un método de unión para que podamos reemplazar lo anterior con una línea de código.
Copie el código de código de la siguiente manera:
System.out.println(String.join(", ", amigos));
Ven a echar un vistazo, los resultados son tan satisfactorios como el código.
Copie el código de código de la siguiente manera:
Brian, Nate, Neal, Raju, Sara, Scott
El resultado sigue siendo bueno, pero este código no favorece. Sálvanos, Java.
Ya no tenemos que soportar este dolor. La clase StringJoiner en Java 8 nos ayuda a resolver estos problemas. No solo eso, la clase String también agrega un método de unión para que podamos reemplazar lo anterior con una línea de código.
Copie el código de código de la siguiente manera:
System.out.println(String.join(", ", amigos));
Ven a echar un vistazo, los resultados son tan satisfactorios como el código.
Copie el código de código de la siguiente manera:
Brian, Nate, Neal, Raju, Sara, Scott
En la implementación subyacente, el método String.join() llama a la clase StringJoiner para unir el valor pasado como segundo parámetro (que es un parámetro de longitud variable) en una cadena larga, utilizando el primer parámetro como delimitador. Por supuesto, este método es más que simplemente empalmar comas. Por ejemplo, podemos pasar un montón de rutas y deletrear fácilmente una ruta de clase, gracias a estos métodos y clases recién agregados.
Ya sabemos cómo conectar elementos de lista. Antes de conectar listas, también podemos transformar los elementos. Por supuesto, también sabemos cómo usar el método de mapa para transformar listas. A continuación, también podemos usar el método filter() para filtrar los elementos que queremos. El último paso para conectar los elementos de la lista, usando comas o algún otro delimitador, es simplemente una simple operación de reducción.
Podemos usar el método reduce() para concatenar los elementos en una cadena, pero esto requiere algo de trabajo de nuestra parte. JDK tiene un método Collect() muy conveniente, que también es una variante de reduce(). Podemos usarlo para combinar elementos en un valor deseado.
El método Collect() realiza la operación de reducción, pero delega la operación específica a un recopilador para su ejecución. Podemos fusionar los elementos convertidos en una ArrayList. Siguiendo con el ejemplo anterior, podemos concatenar los elementos convertidos en una cadena separada por comas.
Copie el código de código de la siguiente manera:
System.out.println(
amigos.stream()
.map(Cadena::aUpperCase)
.collect(uniéndose(", ")));
Llamamos al método Collect() en la lista convertida y le pasamos un recopilador devuelto por el método join(). Joining es un método estático en la clase de herramienta Collectors. El recopilador es como un receptor. Recibe los objetos pasados por recopilación y los almacena en el formato que desee: ArrayList, String, etc. Exploraremos este método más a fondo en el método de recopilación y la clase de Coleccionistas en la página 52.
Este es el nombre de salida, ahora están en mayúsculas y separados por comas.
Copie el código de código de la siguiente manera:
BRIAN, NATE, NEAL, RAJU, SARA, SCOTT
Resumir
Las colecciones son muy comunes en la programación. Con las expresiones lambda, las operaciones de colección de Java se han vuelto más simples y sencillas. Todo el código antiguo y torpe para las operaciones de cobranza se puede reemplazar con este nuevo enfoque elegante y conciso. El iterador interno hace que el recorrido y la transformación de la colección sean más convenientes, evitando el problema de la variabilidad, y encontrar elementos de la colección se vuelve extremadamente fácil. Puede escribir mucho menos código utilizando estos nuevos métodos. Esto hace que el código sea más fácil de mantener, más centrado en la lógica empresarial y menos operaciones básicas en programación.
En el próximo capítulo veremos cómo las expresiones lambda simplifican otra operación básica en el desarrollo de programas: la manipulación de cadenas y la comparación de objetos.