ejemplo - java list map
¿Cuál es la diferencia entre los métodos map y flatMap en Java 8? (17)
El artículo de Oracle en Opcional resalta esta diferencia entre mapa y plano:
String version = computer.map(Computer::getSoundcard)
.map(Soundcard::getUSB)
.map(USB::getVersion)
.orElse("UNKNOWN");
Desafortunadamente, este código no compila. ¿Por qué? La computadora variable es del tipo
Optional<Computer>
, por lo que es perfectamente correcto llamar al método de 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 tipoOptional<Optional<Soundcard>>
. Como resultado, la llamada a getUSB () no es válida porque la Opcional más externa contiene como su valor otra Opcional, que por supuesto no es compatible con el método getUSB ().Con las secuencias, el método flatMap toma una función como un argumento, que devuelve otra secuencia. Esta función se aplica a cada elemento de un flujo, lo que resultaría en un flujo 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 son generados por la función se amalgaman o "aplanan" en un solo flujo. Lo que queremos aquí es algo similar, pero queremos "aplanar" un opcional de dos niveles en uno .
Opcional también soporta un método flatMap. Su propósito es aplicar la función de transformación en el valor de un Opcional (como lo hace la operación del mapa) y luego aplanar el Opcional de dos niveles resultante en uno solo .
Por lo tanto, para que nuestro código sea correcto, debemos reescribirlo de la siguiente manera utilizando flatMap:
String version = computer.flatMap(Computer::getSoundcard)
.flatMap(Soundcard::getUSB)
.map(USB::getVersion)
.orElse("UNKNOWN");
El primer flatMap garantiza que se devuelva una
Optional<Soundcard>
lugar de unaOptional<Optional<Soundcard>>
, y la segunda flatMap logra el mismo propósito para devolver unOptional<USB>
. 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
En Java 8, ¿cuál es la diferencia entre los métodos Stream.map
y Stream.flatMap
?
Esto es muy confuso para los principiantes. La diferencia básica es que el map
emite un elemento para cada entrada en la lista y flatMap
es básicamente una operación map
+ flatten
. Para ser más claros, use flatMap cuando necesite más de un valor, por ejemplo, cuando espera que un bucle devuelva arrays, flatMap será realmente útil en este caso.
He escrito un blog sobre esto, puedes verlo here .
La función que pase a stream.map
tiene que devolver un objeto. Eso significa que cada objeto en el flujo de entrada da como resultado exactamente un objeto en el flujo 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 número de objetos para cada objeto de entrada (incluido ninguno). Los flujos resultantes se concatenan a un flujo de salida.
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 devuelva una secuencia que combine todos los elementos de las secuencias 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 devuelva una nueva secuencia que contenga 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
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 pasada a todos los elementos de la secuencia.
Imaginemos que tengo una lista de valores enteros (1,2,3,4,5) y una interfaz de función cuya lógica es el cuadrado del entero pasado. (e -> e * e).
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> 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 secuencia cuyos valores son cuadrados de valores de la secuencia 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 este flujo, produce un flujo de nuevos valores. Todos los elementos de estos nuevos flujos generados por cada elemento se copian en un nuevo flujo, que será un valor de retorno de este método.
Imaginemos, tengo una lista de objetos de estudiantes, donde cada estudiante puede optar por varias materias.
List<Student> studentList = new ArrayList<Student>();
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<Student> 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 un nuevo flujo cuyos valores son una colección de todos los elementos de los flujos que devuelve cada elemento del flujo de entrada.
[S1, S2, S3] -> [{"history", "math", "geography"}, {"economics", "biology"}, {"science", "math"}] -> toma asignaturas únicas - > [economía, biología, geografía, ciencia, historia, matemáticas]
Me gustaría dar 2 ejemplos para obtener un punto de vista más práctico:
1er ejemplo haciendo uso del mapa:
@Test
public void convertStringToUpperCaseStreams() {
List<String> 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<Integer> together = Stream.of(asList(1, 2), asList(3, 4)) // Stream of List<Integer>
.flatMap(List::stream)
.map(integer -> integer + 1)
.collect(Collectors.toList());
assertEquals(asList(2, 3, 4, 5), together);
}
En el segundo ejemplo, se pasa un flujo de lista. ¡NO es una Corriente de Enteros!
Si se debe usar una función de transformación (a través del mapa), primero se debe aplanar la secuencia a otra cosa (una secuencia de entero).
Si se elimina flatMap, se devuelve el siguiente error: el operador + no está definido para el tipo de argumento Lista, int.
¡NO es posible aplicar +1 en una lista de enteros!
No estoy muy seguro de que deba responder esto, pero cada vez que me enfrento a alguien que no entiende esto, uso el mismo ejemplo.
Imagina que tienes una manzana. Un map
está transformando esa manzana en apple-juice
de apple-juice
por ejemplo, o en un mapa uno a uno .
Tome esa misma manzana y saque solo las semillas, eso es lo que hace flatMap
, o de una a muchas , una manzana como entrada, muchas semillas como salida.
Supongamos que tenemos:
List<List<Integer>> lists = List.of(List.of(1, 2), List.of(3, 4));
mapa plano
Tomemos una Function
que acepta un argumento y produce un Stream
de valores:
Function<List<Integer>, Stream<Integer>> f = list -> list.stream();
Y aplique esta función a cada lista:
for (List<Integer> list : lists) f.apply(list).forEach(System.out::print);
Eso es exactamente lo que puede hacer el Stream.flatMap
:
lists.stream().flatMap(list -> list.stream()).forEach(System.out::print);
mapa
Tomemos una Function
que acepta un argumento y produce un valor:
Function<List<Integer>, String> f = list -> list.toString();
Y aplique esta función a cada lista:
for (List<Integer> list : lists) System.out.print(f.apply(list));
Eso es exactamente lo que puede hacer el Stream.map
:
lists.stream().map(list -> list.toString()).forEach(System.out::print);
También una buena analogía puede ser con C # si está familiarizado con. Básicamente, C # Select
similar a java map
y C # SelectMany
java flatMap
. Lo mismo se aplica a Kotlin para las colecciones.
Tanto map
como flatMap
se pueden aplicar a un Stream<T>
y ambos devuelven un Stream<R>
. La diferencia es que la operación de 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 de cada operación.
La operación de map
toma una Function
, que se llama para cada valor en el flujo de entrada y produce un valor de resultado, que se envía al flujo 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 incómodo para un método devolver un número arbitrario de valores, ya que los métodos solo pueden devolver cero o un valor. Uno podría imaginar una API donde la función del mapeador 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 flujos, una forma particularmente adecuada de representar un número arbitrario de valores de retorno es que la función del asignador devuelva un flujo. Los valores del flujo devuelto por el asignador se drenan del flujo y se pasan al flujo de salida. Los "grupos" de valores devueltos por cada llamada a la función del asignador no se distinguen en absoluto en el flujo de salida, por lo que se dice que la salida se ha "aplanado".
El uso típico es que la función del mapeador de flatMap
devuelva Stream.empty()
si desea enviar valores cero, o algo así como Stream.of(a, b, c)
si desea devolver varios valores. Pero, por supuesto, cualquier flujo puede ser devuelto.
Tengo la sensación de que la mayoría de las respuestas aquí complican el simple problema. Si ya comprende 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 cuando usamos map()
, el método flatMap()
está diseñado para superar esto al evitar el ajuste.
Ejemplos:
1
List<List<Integer>> result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3))
.collect(Collectors.toList());
Podemos evitar tener listas anidadas usando flatMap
:
List<Integer> result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3))
.flatMap(i -> i.stream())
.collect(Collectors.toList());
2
Optional<Optional<String>> result = Optional.of(42)
.map(id -> findById(id));
Optional<String> result = Optional.of(42)
.flatMap(id -> findById(id));
dónde:
private Optional<String> findById(Integer id)
para un Mapa tenemos una lista de elementos y una (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 aplane:
[[a,b],[c,d,e]] f(x) =>[f(a),f(b),f(c),f(d),f(e)]
Stream.flatMap
, como puede adivinarse 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 al flujo sin aplanar el flujo.
Para entender en qué consiste el aplanamiento de una corriente, considere una estructura como [ [1,2,3],[4,5,6],[7,8,9] ]
que tiene "dos niveles". Aplanar esto significa transformarlo en una estructura de "un nivel": [ 1,2,3,4,5,6,7,8,9 ]
.
flatMap()
también aprovecha la evaluación parcial parcial de los flujos. Leerá la primera secuencia y solo cuando sea necesario, irá a la siguiente secuencia. El comportamiento se explica en detalle aquí: ¿Se garantiza que flatMap sea perezoso?
Revise completamente la publicación 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 intentando devolver una lista como ["STACKOVER"] (devolviendo solo letras únicas de esa lista) Inicialmente haríamos algo como a continuación para devolver una lista ["STACKOVER" ] de ["STACK", "OOOVVVER"]
public class WordMap {
public static void main(String[] args) {
List<String> lst = Arrays.asList("STACK","OOOVER");
lst.stream().map(w->w.split("")).distinct().collect(Collectors.toList());
}
}
Aquí el problema es que Lambda pasado al método de mapa devuelve una matriz de Cadena para cada palabra, por lo que la secuencia devuelta por el método de mapa es en realidad de tipo Corriente, pero lo que necesitamos es Corriente para representar una secuencia de caracteres, la imagen de abajo ilustra la problema.
Figura A:
Podrías pensar que, podemos resolver este problema usando un plano,
Bien, veamos cómo resolver esto usando map y Arrays.stream En primer lugar, necesitarás un flujo de caracteres en lugar de un flujo de arrays. Hay un método llamado Arrays.stream () que tomaría una matriz y producirá una secuencia, por ejemplo:
String[] arrayOfWords = {"STACK", "OOOVVVER"};
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);
streamOfWords.map(s->s.split("")) //Converting word in to array of letters
.map(Arrays::stream).distinct() //Make array in to separate stream
.collect(Collectors.toList());
Lo anterior aún no funciona, porque ahora terminamos con una lista de secuencias (más precisamente, Secuencia>). 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:
String[] arrayOfWords = {"STACK", "OOOVVVER"};
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);
streamOfWords.map(s->s.split("")) //Converting word in to array of letters
.flatMap(Arrays::Stream).distinct() //flattens each generated stream in to a single stream
.collect(Collectors.toList());
flatMap realizaría el mapeo de cada matriz, no con el flujo, sino con el contenido de ese flujo. Todos los flujos individuales que se generarían al usar el mapa (Arrays :: flujo) se fusionan en un solo flujo. La Figura B ilustra el efecto de usar el método flatMap. Compáralo con lo que hace el mapa en la figura A. Figura B
El método flatMap le permite reemplazar cada valor de un flujo con otro flujo y luego une todos los flujos generados en un solo flujo.
Respuesta de una línea: flatMap
ayuda a aplanar una Collection<Collection<T>>
en una Collection<T>
. De la misma manera, también aplanará un Optional<Optional<T>>
en Optional<T>
.
Como puedes ver, solo con map()
:
- El tipo intermedio es
Stream<List<Item>>
- El tipo de retorno es
List<List<Item>>
y con flatMap()
:
- El tipo intermedio es
Stream<Item>
- El tipo de retorno es
List<Item>
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<String> items;
public Parcel(String name, String... items) {
this.name = name;
this.items = Arrays.asList(items);
}
public List<String> getItems() {
return items;
}
public static void main(String[] args) {
Parcel amazon = new Parcel("amazon", "Laptop", "Phone");
Parcel ebay = new Parcel("ebay", "Mouse", "Keyboard");
List<Parcel> parcels = Arrays.asList(amazon, ebay);
System.out.println("-------- Without flatMap() -------------------------------");
List<List<String>> mapReturn = parcels.stream()
.map(Parcel::getItems)
.collect(Collectors.toList());
System.out.println("/t collect return: " + mapReturn);
System.out.println("/n-------- With flatMap() ----------------------------------");
List<String> flatMapReturn = parcels.stream()
.map(Parcel::getItems)
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println("/t collect return: " + flatMapReturn);
}
}
mapa () y mapa plano ()
-
map()
Simplemente toma una función un parámetro lambda donde T es el elemento y R el elemento de retorno creado con T. Al final tendremos una secuencia con objetos de 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 valor "prefix_"+integer_value
y lo imprime.
-
flatMap()
Es útil saber que flapMap () toma una función F<T, R>
donde
T es un tipo a partir del 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 un Stream puede ser con / o forma. en el siguiente ejemplo, cada dev tiene muchos idiomas, así que dev. Languages es una lista y usará un parámetro lambda.
R es el flujo resultante que se construirá usando T. Sabiendo que tenemos muchos ejemplos de T, naturalmente tendremos muchos flujos de R. Todos estos flujos de tipo R ahora se combinarán en un solo flujo "plano" de tipo R .
Ejemplo
Los ejemplos de Bachiri Taoufiq ver su respuesta aquí son simples y fáciles 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 dev:
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<List<Languages>> /Object[Languages[]]
. ¡No es tan bonito, ni Java8!
con Stream.flatMap()
puedes ''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<Languages>/Language[]/ect
...
así que al final su código tendría más sentido como este:
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 () :
Utilice
map()
cuando se supone que cada elemento del tipo T de su flujo se debe asignar / transformar en un solo elemento del tipo R. El resultado es un mapeo de tipo (1 elemento de inicio -> 1 elemento final) y un nuevo flujo de elementos de tipo R se devuelve.Use
flatMap()
cuando se supone que cada elemento del tipo T de su flujo debe mapearse / transformarse en colecciones de elementos del tipo R. El resultado es un mapeo del tipo (1 elemento de inicio -> n elementos finales) . Estas colecciones luego se fusionan (o se aplanan ) en una nueva secuencia de elementos de tipo R. Esto es útil, por ejemplo, para representar bucles anidados .
Pre Java 8:
List<Foo> myFoos = new ArrayList<Foo>();
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()));