Cómo ordenar un HashMap por valor en Java

En este tutorial, veremos cómo ordenar un HashMap por valor en Java. Usaremos Java 8 Streams y la clase LinkedHashMap para clasificar en orden ascendente y descendente.

En este tutorial, veremos cómo ordenar un HashMap por valor en Java.

Avancemos y creemos un HashMap simple:

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

unsortedMap.put("John", 21);
unsortedMap.put("Maria", 34);
unsortedMap.put("Mark", 31);
unsortedMap.put("Sydney", 24);

unsortedMap.entrySet().forEach(System.out::println);

Tenemos Strings como claves y Integers como valores. Y nos gustaría ordenar este mapa según los valores.

HashMaps no garantiza mantener el orden de sus elementos en cualquier caso. El orden puede cambiar con el tiempo, y definitivamente no se volverán a imprimir en el orden de inserción:

1
2
3
4
John=21
Mark=31
Maria=34
Sydney=24

Si vuelve a ejecutar este programa, mantendrá este orden, ya que HashMap ordena sus elementos en contenedores, según el valor hash de las claves. Al imprimir valores de un HashMap, su contenido se imprime secuencialmente, por lo que los resultados seguirán siendo los mismos si volvemos a ejecutar el programa varias veces.

Nota: TreeMap amplía la interfaz SortedMap, a diferencia de la implementación HashMap. Los ‘TreeMap’ están destinados a ser la contraparte ordenada, sin embargo, los ‘TreeMap’s solo se clasifican por claves, dado un comparador.

Ordenar HashMap por valor con LinkedHashMap

LinkedHashMap conserva el orden de inserción. Mantiene una lista doblemente enlazada de todas las entradas, lo que le permite acceder de forma muy natural e iterar sobre sus elementos.

Entonces, la forma más fácil de convertir un ‘HashMap’ sin clasificar en un ‘LinkedHashMap’ es agregar los elementos en el orden en que nos gustaría que estuvieran.

Ordenar HashMap en orden ascendente

Para ordenar el unsortedMap que hemos visto anteriormente, crearemos un nuevo LinkedHashMap para albergar los elementos en el orden en que queremos que estén.

Comencemos con la clasificación de un ‘HashMap’ en orden ascendente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Map<String, Integer> sortedMap = unsortedMap.entrySet().stream()
        .sorted(Comparator.comparingInt(e -> e.getValue()))
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (a, b) -> { throw new AssertionError(); },
                LinkedHashMap::new
        ));

sortedMap.entrySet().forEach(System.out::println);

Lo que hemos hecho aquí es transmitir el conjunto de objetos Map.Entry de unsortedMap. Luego, usando el método sorted(), podemos usar varios Comparators para especificar cómo se comparan las entradas.

Ya que estamos tratando con números enteros simples, podemos usar fácilmente el método Comparator.comparingInt() y pasar una expresión lambda. A través de esta expresión, proporcionamos la clave de clasificación del tipo T (en nuestro caso, Integer). Este método luego devuelve un Comparador que compara esa clave de clasificación.

Una vez ordenados, podemos recolectarlos() en un nuevo mapa, a través de la llamada Collectors.toMap(), donde usamos el mismo Map.Entry::getKey y Map.Entry: :getValue como en unsortedMap.

Finalmente, se instancia un nuevo LinkedHashMap, en el que se insertan todos estos elementos, en orden.

Ejecutar este código da como resultado:

1
2
3
4
John=21
Sydney=24
Mark=31
Maria=34

Alternativamente, en lugar de Comparator.comparingInt(), puedes usar Map.Entry.comparingByValue():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Map<String, Integer> sortedMap = unsortedMap.entrySet().stream()
        .sorted(Map.Entry.comparingByValue())
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (a, b) -> { throw new AssertionError(); },
                LinkedHashMap::new
        ));

sortedMap.entrySet().forEach(System.out::println);

Sin embargo, con este enfoque, no puede especificar su propia lógica para las comparaciones. Los valores comparables, como los enteros, se ordenan con la implementación oculta. Sin embargo, podría especificar un objeto personalizado y especificar su propia lógica de comparación en esa clase.

De manera muy similar a esto, también puede simplemente usar Map.Entry::getValue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Map<String, Integer> sortedMap = unsortedMap.entrySet().stream()
        .sorted(Comparator.comparingInt(Map.Entry::getValue))
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (a, b) -> { throw new AssertionError(); },
                LinkedHashMap::new
        ));

sortedMap.entrySet().forEach(System.out::println);

Esto también devuelve:

1
2
3
4
John=21
Sydney=24
Mark=31
Maria=34

Este es funcionalmente igual que el anterior, ya que Map.Entry.comparingByValue() usa el método getValue() para comparar entradas de todos modos.

Ordenar HashMap en orden descendente

Ahora, clasifiquemos el ‘HashMap’ sin clasificar en orden descendente. La única diferencia que necesita hacer es la Expresión Lambda que le hemos proporcionado al método Comparator.comparingInt() - simplemente usaremos -e.getValue() en su lugar:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Map<String, Integer> sortedMap = unsortedMap.entrySet().stream()
        .sorted(Comparator.comparingInt(e -> -e.getValue()))
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (a, b) -> { throw new AssertionError(); },
                LinkedHashMap::new
        ));

sortedMap.entrySet().forEach(System.out::println);

Esto resulta en:

1
2
3
4
Maria=34
Mark=31
Sydney=24
John=21

Este es el beneficio adicional de usar este enfoque, en lugar de Map.Entry.comparingByValue() o Map.Entry::getValue. Puede cambiar fácilmente entre orden descendente y ascendente.

Conclusión

En este tutorial, hemos repasado cómo ordenar un HashMap de Java por valor. Hemos utilizado Java 8 Streams con la clase LinkedHashMap para lograr esta funcionalidad, tanto para ordenar por valores ascendentes como descendentes.

Licensed under CC BY-NC-SA 4.0