¿Cómo filtrar un mapa por sus valores en Java 8?

3 minutos de lectura

Avatar de usuario de Nazila
Nazila

Tengo un mapa donde los valores son cadenas y las claves son listas:

Map<String, List<BoMLine>> materials

Me gustaría filtrar este mapa por sus valores; algo como esto:

materials.entrySet().stream()
       .filter(a -> a.getValue().stream()
           .filter(l -> MaterialDao.findMaterialByName(l.getMaterial()).ispresent)

Pero no está funcionando para mí. Alguien tiene una idea?

Avatar de usuario de Eran
Eran

Si entiendo correctamente sus criterios de filtrado, desea verificar si el filtrado Stream usted produjo a partir del valor List tiene algún elemento, y en caso afirmativo pasar el correspondiente Map entrada a la salida Map.

Map<String, List<BoMLine>>
    filtered = materials.entrySet()
                        .stream()
                        .filter(a->a.getValue()
                                    .stream()
                                    .anyMatch(l->MaterialDao.findMaterialByName(l.getMaterial())))
                        .collect(Collectors.toMap(e->e.getKey(),e->e.getValue()));

esto es asumiendo MaterialDao.findMaterialByName(l.getMaterial()) devuelve un boolean.

  • No estoy seguro de mi solución y no funciona cuando quiero recopilarla en el mapa “e” no es un mapa, es solo un objeto

    – Nazila

    1 de noviembre de 2015 a las 7:25

  • @nazila e es un Map.Entry<String,List<BoMLine>>. Cometí algunos errores de sintaxis tontos en la llamada a collect. Ver respuesta editada.

    – Eran

    1 de noviembre de 2015 a las 7:33

  • No necesitas usar filter()simplemente pase el predicado como argumento a anyMatch().

    – fps

    1 de noviembre de 2015 a las 12:59


  • @FedericoPeraltaSchaffner Gracias. Respondí de memoria sin consultar la API. Lo arreglaré.

    – Eran

    1 de noviembre de 2015 a las 13:03

  • @Eran Intenté filtrar usando lo mismo, pero cuando tengo un valor nulo en uno de los objetos, arroja una excepción de puntero nulo. ¿Cómo puedo evitar eso?

    – Bravo

    13 de diciembre de 2021 a las 18:22

Generalmente, así es como puede filtrar un mapa por sus valores:

static <K, V> Map<K, V> filterByValue(Map<K, V> map, Predicate<V> predicate) {
    return map.entrySet()
            .stream()
            .filter(entry -> predicate.test(entry.getValue()))
            .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}

Llámalo así:

Map<String, Integer> originalMap = new HashMap<>();
originalMap.put("One", 1);
originalMap.put("Two", 2);
originalMap.put("Three", 3);

Map<String, Integer> filteredMap = filterByValue(originalMap, value -> value == 2);

Map<String, Integer> expectedMap = new HashMap<>();
expectedMap.put("Two", 2);

assertEquals(expectedMap, filteredMap);

  • En caso de que alguien se lo pregunte, Entry::getKey & Entry::getValue en el fragmento anterior en realidad se refiere a java.util.Map.Entry::getKey (Necesitar import java.util.Map o java.util.Map.Entry para ello)

    – y2k-shubham

    8 de agosto de 2022 a las 10:05


Todas las respuestas anteriores proporcionan una solución que crea una nueva Map. Esto puede ser ineficaz si desea eliminar solo unas pocas entradas.

Lo siguiente elimina los elementos de la existente Map:

Map<String, Integer> myMap = new HashMap<>();
myMap.put("One", 1);
myMap.put("Two", 2);
myMap.put("Three", 3);

myMap.keySet().removeAll(
        myMap.entrySet().stream()
           .filter(a->a.getValue().equals(2))
                   .map(e -> e.getKey()).collect(Collectors.toList()));

System.out.println(myMap);

Huellas dactilares:

{One=1, Three=3}

¿Como funciona? Recoge todo llaves que necesitan ser removidos en un temporal Listluego los elimina del original Map en una ida. Un temporal List es necesario porque la modificación de un Map invalida flujos e iteradores. Sin embargo, la construcción de un nuevo Map es potencialmente una operación mucho más costosa que construir un List.

Si desea eliminar muchas entradas (50% o más), entonces es mejor crear una nueva Map en efecto.

Avatar de usuario de Nazila
Nazila

Finalmente lo hice funcionar. Mi segundo filtro fue completamente inútil. La respuesta es así:

Map<String, List<BoMLine>>
filtered = materials.entrySet()
                    .stream()
                    .filter(a -> a
                      .getValue()
                       .stream()
                        .allMatch(
                            b -> MaterialDao.findMaterialByName(
                                    b.getMaterial()).isPresent()))
            .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()))

En mi humilde opinión, uno de los códigos Java 8 más concisos puede verse así:

public Map<String, ReportPlaceholderValue> getAllPlaceholders() {
    Map<String, ReportPlaceholderValue> result = chamberDetailsPlaceHolders;

    result.entrySet().stream().filter( e -> !(e.getValue() instanceof StringReportPlaceholderValue) )
       .map( Map.Entry::getKey )
       .collect( Collectors.toList() ).forEach( result.keySet()::remove );

    return result;
}

¿Ha sido útil esta solución?