Cómo filtrar un mapa por clave o valor en Java

En este tutorial, veremos cómo filtrar un mapa por clave o valor en Java 8, usando un bucle for mejorado y el método `filter()` de Stream API, con ejemplos.

Introducción

Las implementaciones Map en Java representan estructuras que asignan claves a valores. Un ‘Mapa’ no puede contener claves duplicadas y cada una puede como máximo ser asignada a un valor. Las implementaciones de Map<K,V> son genéricas y aceptan cualquier K (clave) y V (valor) para mapear.

La interfaz Map también incluye métodos para algunas operaciones básicas (como put(), get(), containsKey(), containsValue(), size(), etc.), operaciones (como putAll() y clear()) y vistas de colección (como keySet(), entrySet() y values()).

Las implementaciones de Map más destacadas utilizadas para fines generales son: HashMap, TreeMap y LinkedHashMap.

En este artículo, veremos cómo filtrar un mapa por sus claves y valores:

Filtrar un mapa con bucles for mejorados

Completemos un HashMap con algunos pares clave-valor:

1
2
3
4
5
6
7
Map<Integer, String> employeeMap = new HashMap<>();

employeeMap.put(35, "Mark");
employeeMap.put(40, "John");
employeeMap.put(23, "Michael");
employeeMap.put(31, "Jim");
employeeMap.put(25, "Kevin");

El Map tiene claves de tipo Integer y valores de tipo String. Representan la edad y el nombre de los empleados.

Filtraremos este mapa por claves y valores y guardaremos los resultados en una ‘Colección’, como otra implementación de ‘Mapa’, o incluso otro ‘HashMap’.

Vamos con LinkedHashMap que conserva el orden de inserción:

1
2
3
4
5
6
7
8
9
Map<Integer, String> linkedHashMap = new LinkedHashMap<>();

for (Map.Entry<Integer, String> employee : employeeMap.entrySet()) {
    if(employee.getKey() > 30){
        linkedHashMap.put(employee.getKey(), employee.getValue());
    }
}
    
System.out.println("Filtered Map: " + linkedHashMap);

Aquí, revisamos el entrySet() del employeeMap y agregamos a cada empleado a un LinkedHashMap a través de su método put(). Esto funcionaría exactamente igual para la implementación de HashMap, pero no preservaría el orden de inserción:

1
Filtered Map: {35=Mark, 40=John, 31=Jim}

Filtrar por valores se reduce al mismo enfoque, aunque verificaremos el valor de cada entrada y lo usaremos en una condición:

1
2
3
4
5
6
7
8
9
Map<Integer, String> linkedHashMap = new LinkedHashMap<>();

for (Map.Entry<Integer, String> employee : employeeMap.entrySet()) {
    if(employee.getValue().equals("Mark")){
        linkedHashMap.put(employee.getKey(), employee.getValue());
    }
}

System.out.println("Filtered Map: " + linkedHashMap);

Y esto daría como resultado:

1
Filtered Map: {35=Mark}

Esta es la forma manual de filtrar un mapa: iterando y seleccionando los elementos deseados. Ahora echemos un vistazo a una forma más legible y amigable: a través de Stream API.

Stream.filter()

Una forma más moderna de filtrar mapas sería aprovechar la API Stream de Java 8, que hace que este proceso sea mucho más legible. El método filter() de la clase Stream, como sugiere el nombre, filtra cualquier Colección en función de una condición dada.

Por ejemplo, dada una ‘Colección’ de nombres, puede filtrarlos en función de condiciones tales como: contener ciertos caracteres o comenzar con un carácter específico.

Filtrar un mapa por claves con Stream.filter()

Aprovechemos la Stream API para filtrar este mismo mapa dada la misma condición. Vamos a transmitir() el entrySet() del mapa, y recolectarlo() de nuevo en un Mapa:

1
2
3
4
5
Map<Integer, String> filteredMap = employeeMap.entrySet()
        .stream().filter(x->x.getKey() > 30)
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

System.out.println("Filtered map: " + filteredMap);

Lo que hace este código es muy similar a lo que hicimos manualmente: para cada elemento en el conjunto del mapa, verificamos si el valor de su clave es mayor que ‘30’ y recopilamos los valores en un nuevo ‘Mapa’. , con sus respectivas claves y valores suministrados a través de las referencias de los métodos getKey() y getValue():

1
Filtered map: {35=Mark, 40=John, 31=Jim}

Filtrar un mapa por valores con Stream.filter()

Ahora, completemos otro mapa, y en lugar de un par clave-valor <Integer, String>, usaremos un par <String, String>:

1
2
3
4
5
6
7
Map<String, String> cityMap = new HashMap<>();

cityMap.put("Tokyo", "Japan");
cityMap.put("Berlin", "Germany");
cityMap.put("Kyoto", "Japan");
cityMap.put("Belgrade", "Serbia");
cityMap.put("Madrid", "Spain");

Esta vez, tenemos pares de ciudad-país, donde las claves son ciudades individuales y los valores son los países en los que se encuentran. Valores no tienen que ser únicos. Kyoto y Tokyo, que son claves únicas, pueden tener el mismo valor - Japón.

Ordenar este mapa por valores, nuevamente, se reduce al mismo enfoque que antes: simplemente usaremos el valor, a través del método getValue() en la condición de filtrado:

1
2
3
4
5
Map<String, String> filteredMap = citiesMap.entrySet()
        .stream().filter(x->"Japan".equals(x.getValue()))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

System.out.println("Filtered map: " + filteredMap)

Ahora, esto da como resultado un mapa filtrado que contiene tanto Tokyo como Kyoto:

1
Filtered map: {Tokyo=Japan, Kyoto=Japan}

Puede ser creativo con los productos y resultados aquí. Por ejemplo, en lugar de colocar estos elementos en un nuevo mapa y devolverlo, también podemos manipular el valor resultante en otras estructuras. Por ejemplo, podríamos filtrar las claves que tienen Japón y Serbia como valores, y unir las claves en una única Cadena:

1
2
3
4
5
6
String filteredMap = citiesMap.entrySet()
            .stream().filter(x-> x.getValue().equals("Japan") || 
                                 x.getValue().equals("Serbia"))
            .map(Map.Entry::getKey).collect(Collectors.joining(", "));

System.out.println("Filtered map: " + filteredMap);

Aquí, hemos usado un ‘Collector’ diferente al anterior. Collectors.joining() devuelve un nuevo Collector que une los elementos en una String. Además del delimitador de CharSequence que hemos pasado, también podríamos haber proporcionado un prefijo de CharSequence y un sufijo de CharSequence para cada elemento unido.

Esto da como resultado una ‘Cadena’, con todos los elementos filtrados, separados por un ‘,’:

1
Filtered map: Belgrade, Tokyo, Kyoto

Conclusión

En este artículo, hemos echado un vistazo a cómo filtrar un Mapa en Java. Primero analizamos cómo usar bucles for mejorados para proyectos anteriores a Java 8, después de lo cual nos sumergimos en la API de Steam y aprovechamos el método filter().

Filtrar mapas por valores o claves se convierte en una tarea simple de una sola línea con la ayuda de Stream API, y tiene una amplia variedad de Collectors para formatear la salida a su gusto.

Licensed under CC BY-NC-SA 4.0