Uso Básico de map en Streams Java
El caso más sencillo: convertir todos los elementos de una colección a mayúsculas.
List<String> animales = Arrays.asList("Perro", "Gato", "Elefante", "Tigre");
//Sin utilizar Stream
List<String> animalesMayusculas = new ArrayList<>();
for (String animal : animales) {
animalesMayusculas.add(animal.toUpperCase());
}
System.out.println(animalesMayusculas); //[PERRO, GATO, ELEFANTE, TIGRE]
// Utilizando Stream
List<String> resultado = animales.stream().map(String::toUpperCase).collect(Collectors.toList());
//La línea anterior usa referencia de método, equivalente a esta expresión lambda
//List<String> resultado = animales.stream().map(animal -> animal.toUpperCase()).collect(Collectors.toList());
System.out.println(resultado); //[PERRO, GATO, ELEFANTE, TIGRE]
La función map transforma cada elemento en el Stream según la operación especificada.
Transformación de Colecciones con Tipos no Textuales
El método map() permite no solo procesar datos, sino también cambiar su tipo. Por ejemplo:
List<Integer> longitudes = animales.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(longitudes); //[5, 4, 8, 5]
Stream.of("Perro", "Gato", "Elefante", "Tigre")
.mapToInt(String::length)
.forEach(System.out::println);
La salida sería:
5
4
8
5
Además de mapToInt, existen mapToLong, mapToDouble y variantes similares.
Transformaciones Complejas con Objetos
Utilizando una clase Persona con 10 instancias, necesitamos:
- Incrementar en uno la edad de cada Persona
- Reemplazar "H" por "hombre" y "M" por "mujer" en el campo de género.
public static void main(String[] args){
Persona p1 = new Persona(1, 23, "H", "Carlos", "Pérez");
Persona p2 = new Persona(2, 13, "M", "Ana", "Gómez");
Persona p3 = new Persona(3, 43, "H", "Luis", "Martínez");
Persona p4 = new Persona(4, 26, "H", "Juan", "López");
Persona p5 = new Persona(5, 19, "M", "Sofía", "Rodríguez");
Persona p6 = new Persona(6, 15, "H", "Miguel", "Sánchez");
Persona p7 = new Persona(7, 68, "M", "María", "García");
Persona p8 = new Persona(8, 79, "H", "Pedro", "Fernández");
Persona p9 = new Persona(9, 15, "M", "Laura", "Muñoz");
Persona p10 = new Persona(10, 45, "H", "David", "Jiménez");
List<Persona> personas = Arrays.asList(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10);
List<Persona> transformadas = personas.stream()
.peek(p -> {
p.setEdad(p.getEdad() + 1);
p.setGenero(p.getGenero().equals("H")?"hombre":"mujer");
}).collect(Collectors.toList());
System.out.println(transformadas);
}
Dado que el parámetro de map es también el valor de retorno, podemos usar peek. peek es un caso especial de map cuando la función no devuelve valor o cuando el parámetro es el mismo que el valor de retorno.
flatMap
map transforma elementos en un Stream, pero ¿cómo manejar Streams dentro de Streams? Es decir, ¿cómo procesar arrays bidimensionales o colecciones anidadas? Consideremos un ejemplo: imprimir cada letra de una colección con las palabras "hola", "mundo". Sin Streams, necesitaríamos dos bucles for: uno para recorer las palabras y otro para cada letra.
List<String> palabras = Arrays.asList("hola", "mundo");
palabras.stream()
.map(p -> Arrays.stream(p.split(""))) //[[h,o,l,a],[m,u,n,d,o]]
.forEach(System.out::println);
El resultado impreso sería:
java.util.stream.ReferencePipeline$Head@3551a94
java.util.stream.ReferencePipeline$Head@531be3c5
- Con map no es posible lograrlo. map solo opera con arrrays unidimensionales. Cuando hay arays dentro de arrays o Streams dentro de Streams, no puede procesar cada elemento individualmente.
- flatMap "aplana" varios Streams secundarios en uno único, combinando todos los elementos.
palabras.stream()
.flatMap(p -> Arrays.stream(p.split(""))) // [h,o,l,a,m,u,n,d,o]
.forEach(System.out::println);
El resultado impreso sería:
h
o
l
a
m
u
n
d
o