Trabajar con mapas mediante secuencias

1. Introducción

En este tutorial, discutiremos algunos ejemplos de cómo usar Java Stream spara trabajar con Map s. Vale la pena señalar que algunos de estos ejercicios podrían resolverse utilizando una estructura de datos de mapa bidireccional , pero estamos interesados ​​aquí en un enfoque funcional.

Primero, explicamos la idea básica que usaremos para trabajar con Maps y Stream . A continuación, presentamos un par de problemas diferentes relacionados con Maps y sus soluciones concretas utilizando Stream s.

2. Idea básica

Lo principal a tener en cuenta es que los Stream son secuencias de elementos que se pueden obtener fácilmente de una colección .

Los mapas tienen una estructura diferente, con un mapeo de claves a valores, sin secuencia. Esto no significa que no podamos convertir una estructura de mapa en diferentes secuencias que luego nos permitan trabajar de forma natural con la API Stream.

Veamos formas de obtener diferentes colecciones de un mapa , que luego podemos convertir en una secuencia :

Map someMap = new HashMap();

Podemos obtener un conjunto de pares clave-valor:

Set
    
      entries = someMap.entrySet();
    

También podemos obtener el conjunto de claves asociado con el Mapa :

Set keySet = someMap.keySet();

O podríamos trabajar directamente con el conjunto de valores:

Collection values = someMap.values();

Cada uno de estos nos da un punto de entrada para procesar esas colecciones obteniendo flujos de ellos:

Stream
    
      entriesStream = entries.stream(); Stream valuesStream = values.stream(); Stream keysStream = keySet.stream();
    

3. Obtención de las claves de un mapa utilizando Stream s

3.1. Los datos de entrada

Supongamos que tenemos un mapa :

Map books = new HashMap(); books.put( "978-0201633610", "Design patterns : elements of reusable object-oriented software"); books.put( "978-1617291999", "Java 8 in Action: Lambdas, Streams, and functional-style programming"); books.put("978-0134685991", "Effective Java");

Estamos interesados ​​en encontrar el ISBN del libro con el título “Eficaz Java”.

3.2. Recuperar una coincidencia

Dado que el título del libro no podría existir en nuestro mapa , queremos poder indicar que no hay un ISBN asociado para él. Podemos usar un Opcional para expresar que :

Supongamos para este ejemplo que estamos interesados ​​en cualquier clave de un libro que coincida con ese título:

Optional optionalIsbn = books.entrySet().stream() .filter(e -> "Effective Java".equals(e.getValue())) .map(Map.Entry::getKey) .findFirst(); assertEquals("978-0134685991", optionalIsbn.get());

Analicemos el código. Primero, obtenemos el entrySet del Map , como vimos anteriormente.

Queremos considerar solo las entradas con "Java efectivo" como título, por lo que la primera operación intermedia será un filtro.

No nos interesa la entrada completa del Mapa , sino la clave de cada entrada. Entonces, la siguiente operación intermedia encadenada hace precisamente eso: es una operación de mapa que generará una nueva secuencia como salida que contendrá solo las claves para las entradas que coincidieron con el título que estábamos buscando.

Como solo queremos un resultado, podemos aplicar la operación de terminal findFirst () , que proporcionará el valor inicial en la secuencia como un objeto opcional .

Veamos un caso en el que no existe un título:

Optional optionalIsbn = books.entrySet().stream() .filter(e -> "Non Existent Title".equals(e.getValue())) .map(Map.Entry::getKey).findFirst(); assertEquals(false, optionalIsbn.isPresent());

3.3. Recuperar varios resultados

Cambiemos el problema ahora para ver cómo podemos lidiar con la devolución de múltiples resultados en lugar de uno.

Para obtener varios resultados, agreguemos el siguiente libro a nuestro mapa :

books.put("978-0321356680", "Effective Java: Second Edition"); 

Así que ahora, si buscamos todos los libros que comienzan con "Java efectivo", obtendremos más de un resultado:

List isbnCodes = books.entrySet().stream() .filter(e -> e.getValue().startsWith("Effective Java")) .map(Map.Entry::getKey) .collect(Collectors.toList()); assertTrue(isbnCodes.contains("978-0321356680")); assertTrue(isbnCodes.contains("978-0134685991"));

Lo que hemos hecho en este caso es reemplazar la condición de filtro para verificar si el valor en el mapa comienza con "Java efectivo" en lugar de comparar la igualdad de cadenas .

Esta vez, nos recogen los resultados - en lugar de recoger la primera - poner los partidos en una lista .

4. Obtención de los valores de un mapa utilizando Stream s

Ahora, centrémonos en un problema diferente con los mapas: en lugar de obtener ISBN basados ​​en los títulos , intentaremos obtener títulos basados ​​en los ISBN.

Usemos el mapa original . Queremos encontrar títulos cuyo ISBN comience con “978-0”.

List titles = books.entrySet().stream() .filter(e -> e.getKey().startsWith("978-0")) .map(Map.Entry::getValue) .collect(Collectors.toList()); assertEquals(2, titles.size()); assertTrue(titles.contains( "Design patterns : elements of reusable object-oriented software")); assertTrue(titles.contains("Effective Java"));

Esta solución es similar a las soluciones a nuestro conjunto de problemas anterior: transmitimos el conjunto de entrada y luego filtramos, mapeamos y recopilamos.

Y como antes, si quisiéramos devolver solo la primera coincidencia, podríamos, después de que el método del mapa , llamar al método findFirst () en lugar de recopilar todos los resultados en una Lista .

5. Conclusión

Hemos mostrado cómo procesar un mapa de forma funcional .

En particular, hemos visto que una vez que pasamos a usar las colecciones asociadas a los mapas , el procesamiento con Stream se vuelve mucho más fácil e intuitivo.

Y, por supuesto, todos los ejemplos se pueden encontrar en el proyecto GitHub.