¿Cuál es la diferencia entre los métodos map y flatMap en Java 8?

En Java 8, ¿cuál es la diferencia entre los métodos Stream.map y Stream.flatMap ?

Tanto el map como el map flatMap se pueden aplicar a un Stream y ambos devuelven un Stream . La diferencia es que la operación del map produce un valor de salida para cada valor de entrada, mientras que la operación flatMap produce un número arbitrario (cero o más) valores para cada valor de entrada.

Esto se refleja en los argumentos para cada operación.

La operación de map toma una Function , que se llama para cada valor en la secuencia de entrada y produce un valor de resultado, que se envía a la secuencia de salida.

La operación flatMap toma una función que conceptualmente quiere consumir un valor y producir un número arbitrario de valores. Sin embargo, en Java, es engorroso que un método devuelva un número arbitrario de valores, ya que los métodos pueden devolver solo cero o un valor. Uno podría imaginar una API donde la función mapper para flatMap toma un valor y devuelve una matriz o una List de valores, que luego se envían a la salida. Dado que esta es la biblioteca de secuencias, una forma particularmente adecuada para representar un número arbitrario de valores devueltos es que la función del asignador devuelva una secuencia. Los valores de la secuencia devuelta por el asignador se vacían de la secuencia y se pasan a la secuencia de salida. Los “grupos” de valores devueltos por cada llamada a la función del asignador no se distinguen en absoluto en la secuencia de salida, por lo tanto, se dice que la salida se ha “aplanado”.

El uso típico es para la función mapeador de flatMap para devolver Stream.empty() si desea enviar valores cero, o algo así como Stream.of(a, b, c) si quiere devolver varios valores. Pero, por supuesto, cualquier secuencia puede ser devuelta.

Stream.flatMap , como se puede adivinar por su nombre, es la combinación de un map y una operación flat . Eso significa que primero aplica una función a sus elementos y luego la aplana. Stream.map solo aplica una función a la transmisión sin aplanar la transmisión.

Para entender en qué consiste el aplanamiento de una stream, considere una estructura como [ [1,2,3],[4,5,6],[7,8,9] ] que tiene “dos niveles”. Aplastar esto significa transformarlo en una estructura de “un nivel”: [ 1,2,3,4,5,6,7,8,9 ] .

Me gustaría dar 2 ejemplos para obtener un punto de vista más práctico:
Primer ejemplo haciendo uso del mapa:

 @Test public void convertStringToUpperCaseStreams() { List collected = Stream.of("a", "b", "hello") // Stream of String .map(String::toUpperCase) // Returns a stream consisting of the results of applying the given function to the elements of this stream. .collect(Collectors.toList()); assertEquals(asList("A", "B", "HELLO"), collected); } 

Nada especial en el primer ejemplo, se aplica una Function para devolver la String en mayúsculas.

Segundo ejemplo haciendo uso de flatMap :

 @Test public void testflatMap() throws Exception { List together = Stream.of(asList(1, 2), asList(3, 4)) // Stream of List .flatMap(List::stream) .map(integer -> integer + 1) .collect(Collectors.toList()); assertEquals(asList(2, 3, 4, 5), together); } 

En el segundo ejemplo, se pasa una secuencia de lista. ¡NO es una stream de enteros!
Si se debe utilizar una función de transformación (a través del mapa), primero se debe aplanar la secuencia a otra cosa (una secuencia de enteros).
Si se elimina flatMap, se devuelve el siguiente error: El operador + no está definido para los tipos de argumento List, int.
¡NO es posible aplicar +1 en una Lista de enteros!

Revise la publicación por completo para obtener una idea clara, map vs flatMap: para devolver una longitud de cada palabra de una lista, haríamos algo como a continuación.

Por ejemplo:-
Considere una lista [“STACK”, “OOOVVVER”] y estamos tratando de devolver una lista como [“STACKOVER”] (devolviendo únicamente letras únicas de esa lista) Inicialmente, haríamos algo como a continuación para devolver una lista [“STACKOVER” ] desde [“STACK”, “OOOVVVER”]

 public class WordMap { public static void main(String[] args) { List lst = Arrays.asList("STACK","OOOVER"); lst.stream().map(w->w.split("")).distinct().collect(Collectors.toList()); } } 

Aquí el problema es que Lambda pasó al método del mapa y devuelve un conjunto de cadenas para cada palabra, por lo que la secuencia devuelta por el método del mapa es realmente del tipo Stream, pero lo que necesitamos es que Stream represente una secuencia de caracteres, debajo de la imagen se ilustra problema.

Figura A:

enter image description here

Podrías pensar que, podemos resolver este problema usando flatmap,
Bien, veamos cómo resolver esto usando map y Arrays.stream Antes que nada necesitarás una secuencia de caracteres en lugar de una secuencia de matrices. Existe un método llamado Arrays.stream () que tomaría una matriz y generaría una secuencia, por ejemplo: enter image description here

Lo anterior aún no funciona, porque ahora terminamos con una lista de secuencias (más precisamente, Stream>). En su lugar, primero debemos convertir cada palabra en una matriz de letras individuales y luego hacer cada matriz en una secuencia separada

Al usar flatMap, deberíamos poder solucionar este problema de la siguiente manera:

enter image description here

flatMap realizaría el mapeo de cada matriz, no con la secuencia, sino con el contenido de esa secuencia. Todas las secuencias individuales que se generarían durante el uso del mapa (Arrays :: stream) se fusionarían en una única secuencia. La Figura B ilustra el efecto del uso del método flatMap. Compáralo con lo que hace el mapa en la figura A. Figura B enter image description here

El método flatMap le permite reemplazar cada valor de una secuencia con otra stream y luego une todas las secuencias generadas en una sola transmisión.

La función que pasas a stream.map tiene que devolver un objeto. Eso significa que cada objeto en la secuencia de entrada da como resultado exactamente un objeto en la secuencia de salida.

La función que pasa a stream.flatMap devuelve una secuencia para cada objeto. Eso significa que la función puede devolver cualquier cantidad de objetos para cada objeto de entrada (incluido ninguno). Las secuencias resultantes se concatenan a una secuencia de salida.

para un Mapa tenemos una lista de elementos y a (función, acción) f así:

 [a,b,c] f(x) => [f(a),f(b),f(c)] 

y para el mapa plano tenemos una lista de elementos y tenemos una (función, acción) f y queremos que el resultado se aplana:

 [[a,b],[c,d,e]] f(x) =>[f(a),f(b),f(c),f(d),f(e)] 

Tengo la sensación de que la mayoría de las respuestas aquí complican el simple problema. Si ya entiende cómo funciona el map debería ser bastante fácil de entender.

Hay casos en los que podemos terminar con estructuras anidadas no deseadas al usar map() , el método flatMap() está diseñado para superar esto al evitar el ajuste.


Ejemplos:

1

 List> result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3)) .collect(Collectors.toList()); 

Podemos evitar tener listas anidadas usando flatMap :

 List result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3)) .flatMap(i -> i.stream()) .collect(Collectors.toList()); 

2

 Optional> result = Optional.of(42) .map(id -> findById(id)); Optional result = Optional.of(42) .flatMap(id -> findById(id)); 

dónde:

 private Optional findById(Integer id) 

El artículo de Oracle sobre Opcional resalta esta diferencia entre mapa y mapa plano:

 String version = computer.map(Computer::getSoundcard) .map(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN"); 

Lamentablemente, este código no se comstack. ¿Por qué? La computadora variable es del tipo Optional , por lo que es perfectamente correcto llamar al método del mapa. Sin embargo, getSoundcard () devuelve un objeto de tipo Opcional. Esto significa que el resultado de la operación de mapa es un objeto de tipo Optional> . Como resultado, la llamada a getUSB () no es válida porque la Opción más externa contiene como su valor otra Opcional, que por supuesto no admite el método getUSB ().

Con las secuencias, el método flatMap toma una función como argumento, que devuelve otra secuencia. Esta función se aplica a cada elemento de una secuencia, lo que daría como resultado una secuencia de flujos. Sin embargo, flatMap tiene el efecto de reemplazar cada flujo generado por el contenido de ese flujo. En otras palabras, todos los flujos separados que se generan por la función se fusionan o “aplastan” en una sola secuencia. Lo que queremos aquí es algo similar, pero queremos “aplanar” un Opcional de dos niveles en uno .

Opcional también es compatible con un método flatMap. Su propósito es aplicar la función de transformación en el valor de un Opcional (tal como lo hace la operación de mapa) y luego aplanar el Opcional de dos niveles resultante en uno solo .

Entonces, para hacer que nuestro código sea correcto, necesitamos reescribirlo de la siguiente manera usando flatMap:

 String version = computer.flatMap(Computer::getSoundcard) .flatMap(Soundcard::getUSB) .map(USB::getVersion) .orElse("UNKNOWN"); 

El primer flatMap asegura que se devuelve una Optional lugar de una Optional> , y la segunda Carta plana logra el mismo propósito para devolver un Optional . Tenga en cuenta que la tercera llamada solo necesita ser un mapa () porque getVersion () devuelve una cadena en lugar de un objeto opcional.

http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

Mapa: este método toma una función como argumento y devuelve una nueva secuencia que consiste en los resultados generados al aplicar la función aprobada a todos los elementos de la secuencia.

Imaginemos, tengo una lista de valores enteros (1,2,3,4,5) y una interfaz de función cuya lógica es cuadrada del entero pasado. (e -> e * e).

 List intList = Arrays.asList(1, 2, 3, 4, 5); List newList = intList.stream().map( e -> e * e ).collect(Collectors.toList()); System.out.println(newList); 

salida:-

 [1, 4, 9, 16, 25] 

Como puede ver, una salida es una nueva stream cuyos valores son cuadrados de valores de la stream de entrada.

 [1, 2, 3, 4, 5] -> apply e -> e * e -> [ 1*1, 2*2, 3*3, 4*4, 5*5 ] -> [1, 4, 9, 16, 25 ] 

http://codedestine.com/java-8-stream-map-method/

FlatMap: – Este método toma una función como argumento, esta función acepta un parámetro T como argumento de entrada y devuelve una secuencia del parámetro R como valor de retorno. Cuando esta función se aplica a cada elemento de esta secuencia, produce una secuencia de nuevos valores. Todos los elementos de estas nuevas transmisiones generadas por cada elemento se copian en una nueva secuencia, que será un valor de retorno de este método.

Imaginemos, tengo una lista de objetos estudiantiles, donde cada estudiante puede optar por múltiples materias.

 List studentList = new ArrayList(); studentList.add(new Student("Robert","5st grade", Arrays.asList(new String[]{"history","math","geography"}))); studentList.add(new Student("Martin","8st grade", Arrays.asList(new String[]{"economics","biology"}))); studentList.add(new Student("Robert","9st grade", Arrays.asList(new String[]{"science","math"}))); Set courses = studentList.stream().flatMap( e -> e.getCourse().stream()).collect(Collectors.toSet()); System.out.println(courses); 

salida:-

 [economics, biology, geography, science, history, math] 

Como puede ver, una salida es una nueva secuencia cuyos valores son una colección de todos los elementos de las secuencias devueltos por cada elemento de la secuencia de entrada.

[S1, S2, S3] -> [{“historia”, “matemática”, “geografía”}, {“economía”, “biología”}, {“ciencia”, “matemática”}] -> tome asignaturas únicas – > [economía, biología, geografía, ciencia, historia, matemática]

http://codedestine.com/java-8-stream-flatmap-method/

Respuesta de una línea: flatMap ayuda a aplanar una Collection> en una Collection .

enter image description here

Como puede ver, con el map() solamente:

  • El tipo intermedio es Stream>
  • El tipo de devolución es List>

y con flatMap() :

  • El tipo intermedio es Stream
  • El tipo de devolución es List

Este es el resultado de la prueba del código utilizado a continuación:

 -------- Without flatMap() ------------------------------- collect return: [[Laptop, Phone], [Mouse, Keyboard]] -------- With flatMap() ---------------------------------- collect return: [Laptop, Phone, Mouse, Keyboard] 

Código utilizado :

 import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; public class Parcel { String name; List items; public Parcel(String name, String... items) { this.name = name; this.items = Arrays.asList(items); } public List getItems() { return items; } public static void main(String[] args) { Parcel amazon = new Parcel("amazon", "Laptop", "Phone"); Parcel ebay = new Parcel("ebay", "Mouse", "Keyboard"); List parcels = Arrays.asList(amazon, ebay); System.out.println("-------- Without flatMap() -------------------------------"); List> mapReturn = parcels.stream() .map(Parcel::getItems) .collect(Collectors.toList()); System.out.println("\t collect return: " + mapReturn); System.out.println("\n-------- With flatMap() ----------------------------------"); List flatMapReturn = parcels.stream() .map(Parcel::getItems) .flatMap(Collection::stream) .collect(Collectors.toList()); System.out.println("\t collect return: " + flatMapReturn); } } 

map () y flatMap ()

  1. map()

Simplemente toma una función un parametro lambda donde T es un elemento y R el elemento de retorno se construye usando T. Al final tendremos un flujo con objetos del tipo R. Un ejemplo simple puede ser:

 Stream .of(1,2,3,4,5) .map(myInt -> "preFix_"+myInt) .forEach(System.out::println); 

Simplemente toma los elementos 1 a 5 de Type Integer , usa cada elemento para construir un nuevo elemento de tipo String con el valor "prefix_"+integer_value y lo imprime.

  1. flatMap()

Es útil saber que flapMap () toma una función F donde

  • T es un tipo desde el cual se puede construir un Stream desde / con . Puede ser una Lista (T.stream ()), una matriz (Arrays.stream (someArray)), etc. cualquier cosa desde la cual una Stream pueda estar con / o forma. en el siguiente ejemplo, cada desarrollador tiene muchos idiomas, así que dev. Languages ​​es una lista y usará un parámetro lambda.

  • R es la secuencia resultante que se generará usando T. Sabiendo que tenemos muchas instancias de T, naturalmente tendremos muchas secuencias de R. Todas estas secuencias de Tipo R ahora se combinarán en una única transmisión “plana” de Tipo R .

Los ejemplos de Bachiri Taoufiq ven que su respuesta aquí es simple y fácil de entender. Solo por claridad, digamos que tenemos un equipo de desarrolladores:

 dev_team = {dev_1,dev_2,dev_3} 

, con cada desarrollador conociendo muchos idiomas:

 dev_1 = {lang_a,lang_b,lang_c}, dev_2 = {lang_d}, dev_2 = {lang_e,lang_f} 

Aplicando Stream.map () en dev_team para obtener los idiomas de cada desarrollador:

 dev_team.map(dev -> dev.getLanguages()) 

te dará esta estructura:

 { {lang_a,lang_b,lang_c}, {lang_d}, {lang_e,lang_f} } 

que es básicamente una List> /Object[Languages[]] . ¡No muy bonito, ni parecido a Java8!

con Stream.flatMap() puede ‘aplanar’ las cosas ya que toma la estructura anterior
y lo convierte en {lang_a, lang_b, lang_c, lang_d, lang_e, lang_f} , que básicamente se puede usar como List/Language[]/ect

así que al final tu código tendría más sentido así:

 dev_team .stream() /* {dev_1,dev_2,dev_3} */ .map(dev -> dev.getLanguages()) /* {{lang_a,...,lang_c},{lang_d}{lang_e,lang_f}}} */ .flatMap(languages -> languages.stream()) /* {lang_a,...,lang_d, lang_e, lang_f} */ .doWhateverWithYourNewStreamHere(); 

o simplemente:

 dev_team .stream() /* {dev_1,dev_2,dev_3} */ .flatMap(dev -> dev.getLanguages().stream()) /* {lang_a,...,lang_d, lang_e, lang_f} */ .doWhateverWithYourNewStreamHere(); 

Cuándo usar map () y usar flatMap () :

  • Use map() cuando se supone que cada elemento de tipo T de la secuencia debe asignarse / transformarse a un único elemento de tipo R. El resultado es una asignación de tipo (1 elemento de inicio -> 1 elemento final) y una nueva secuencia de elementos de tipo R se devuelve.

  • Utilice flatMap() cuando se supone que cada elemento de tipo T de la secuencia debe correlacionarse / transformarse en una Colección de elementos de tipo R. El resultado es una asignación de tipo (1 elemento de inicio -> n elementos finales) . Estas colecciones se fusionan (o aplastan ) con una nueva secuencia de elementos de tipo R. Esto es útil, por ejemplo, para representar bucles nesteds .

Pre Java 8:

 List myFoos = new ArrayList(); for(Foo foo: myFoos){ for(Bar bar: foo.getMyBars()){ System.out.println(bar.getMyName()); } } 

Publicar Java 8

 myFoos .stream() .flat(foo -> foo.getMyBars().stream()) .forEach(bar -> System.out.println(bar.getMyName())); 

También una buena analogía puede ser con C # si está familiarizado. Básicamente C # Select similar al map java y C # SelectMany java flatMap . Lo mismo aplica a Kotlin para colecciones.

Esto es muy confuso para principiantes. La diferencia básica es que el map emite un elemento por cada entrada en la lista y flatMap es básicamente una operación de map + flatten . Para ser más claros, use flatMap cuando necesite más de un valor, por ejemplo, cuando espera que un bucle devuelva matrices, flatMap será de gran ayuda en este caso.

He escrito un blog sobre esto, puedes verlo aquí .

La diferencia queda clara si se toma un ejemplo simple:

 List> matrix = List.of(List.of(1, 2), List.of(3, 4)); 

El objective: imprimir todos los valores de la matriz (de cualquier manera).

mapa plano

Tomemos una Function que acepta un argumento y produce un Stream :

 Function, Stream> fn = list -> list.stream(); 

Y aplique esta función a cada elemento (lista) en la matriz:

 for (List list : matrix) { Stream stream = fn.apply(list); stream.forEach(System.out::print); } 

Que es exactamente lo que Stream.flatMap(fn) puede lograr de una manera concisa:

 matrix.stream().flatMap(fn).forEach(System.out::print); 

IMPORTANTE: fn.apply(list) – acepta una List y produce una Stream .

mapa

Tomemos una Function que acepta un argumento y produce un value :

 Function, String> fn = list -> list.toString(); 

Y aplique esta función a cada elemento (lista) en la matriz:

 for (List list : matrix) { String str = fn.apply(list); System.out.print(str); } 

Que es exactamente lo que Stream.map(fn) puede lograr de forma concisa:

 matrix.stream().map(fn).forEach(System.out::print); 

IMPORTANTE: fn.apply(list) – acepta una List y produce una String .

Las operaciones de flujo flatMap y map aceptan una función como entrada.

flatMap espera que la función devuelva una nueva secuencia para cada elemento de la secuencia y devuelve una secuencia que combina todos los elementos de las transmisiones devueltas por la función para cada elemento. En otras palabras, con flatMap , para cada elemento de la fuente, la función creará múltiples elementos. http://www.zoftino.com/java-stream-examples#flatmap-operation

map espera que la función devuelva un valor transformado y devuelve una nueva secuencia que contiene los elementos transformados. En otras palabras, con el map , para cada elemento de la fuente, la función creará un elemento transformado. http://www.zoftino.com/java-stream-examples#map-operation