Cómo ordenar un HashMap por clave en Java

En este tutorial, cubriremos cómo ordenar un HashMap por clave en Java, usando TreeMap y LinkedHashMap, ambos con comparadores predeterminados y personalizados, en orden ascendente y descendente.

En este tutorial, veremos cómo ordenar un HashMap por clave 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. La mayoría de las veces, encontrará Integers o Strings como claves y objetos personalizados, Strings o Integers como valores. Querremos ordenar este HashMap, basado en las claves String.

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.

Ordenar HashMap por clave con TreeMap

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 claves de cadena lexicográficamente

Crear un ‘TreeMap’, dado un ‘HashMap’, es tan fácil como proporcionar la llamada al constructor con el mapa sin clasificar:

1
2
Map<String, Integer> sortedMap = new TreeMap<>(unsortedMap);
sortedMap.entrySet().forEach(System.out::println);

Ejecutar este código da como resultado:

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

Como no proporcionamos ningún comparador, se activa el comparador predeterminado que se usa para las cadenas. Específicamente, cuando compara cadenas, el método compareTo() compara el valor lexicográfico de cada cadena y las ordena en orden ascendente.

Veremos nombres que comienzan con A, antes de nombres que comienzan con B, etc. Agreguemos dos nombres nuevos y veamos qué sucede:

1
2
3
4
5
unsortedMap.put("Adam", 35);
unsortedMap.put("Aaron", 22);
        
Map<String, Integer> sortedMap = new TreeMap<>(unsortedMap);
sortedMap.entrySet().forEach(System.out::println);

Esto resulta en:

1
2
3
4
5
6
Aaron=22
Adam=35
John=21
Maria=34
Mark=31
Sydney=24

Ordenar claves con comparador personalizado

Una característica realmente interesante es que podemos proporcionar un nuevo Comparator<T>() al TreeMap y especificar nuestra propia lógica de comparación en él. Por ejemplo, echemos un vistazo a cómo podemos ordenar las claves de cadena por longitud en un ‘HashMap’, usando la ’longitud’ de las cadenas y un comparador personalizado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Map<String, Integer> sortedMap = new TreeMap<>(new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        int lengthDifference = o1.length() - o2.length();
        if (lengthDifference != 0) return lengthDifference;
        return o1.compareTo(o2);
    }
});

sortedMap.putAll(unsortedMap);

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

Aquí, hemos construido un ‘TreeMap’ con un ‘Comparador’ personalizado y en el método anulado ‘comparar()’, hemos especificado nuestra lógica deseada.

Dado que no tenemos garantía de que o1.length() - o2.length() no sea 0, una simple declaración if asegura que los comparemos lexicográficamente si sus longitudes son las mismas.

Luego, una vez que hemos especificado los criterios de clasificación para el TreeMap, hemos usado el método putAll() para insertar todos los elementos del unsortedMap en el sortedMap.

Ejecutar este código da como resultado:

1
2
3
4
5
6
Adam=35
John=21
Mark=31
Aaron=22
Maria=34
Sydney=24

El mapa ahora se ordena a través de un ‘Comparador’ personalizado, que en este caso compara la ’longitud’ de las claves de ‘Cadena’. Puede usar cualquier lógica aquí para adaptarse a sus necesidades específicas.

Ordenar HashMap por clave 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 claves de HashMap lexicográficamente

Ahora, avancemos y clasifiquemos el unsortedMap, creando un nuevo LinkedHashMap que contendrá los elementos, en orden.

La clase Map.Entry tiene un método muy útil que entra en juego aquí: comparingByKey(), que compara las claves si tienen métodos de comparación válidos. Ya que estamos tratando con Strings, este es el método compareTo(), que una vez más clasificará las Strings lexicográficamente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Map<String, Integer> sortedMap = unsortedMap.entrySet().stream()
        .sorted(Map.Entry.comparingByKey())
        .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, utilizando el método sorted(), proporcionamos el útil Comparator generado por comparingByKey(), que compara los objetos dados con su implementación de comparación predeterminada.

Una vez ordenados, los recolectamos(), usando Collectors.toMap(), en un nuevo mapa. Por supuesto, usaremos las mismas claves y valores del mapa original, a través de las referencias del método Map.Entry::getKey y Map.Entry::getValue.

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
5
6
Aaron=22
Adam=35
John=21
Maria=34
Mark=31
Sydney=24

Ordenar claves HashMap con comparador personalizado

Alternativamente, puede usar su propio ‘Comparador’ en lugar del generado por ‘Map.Entry.comparingByKey()’. Esto es tan fácil como proporcionar un Comparator.comparing() y pasarle una expresión lambda válida:

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

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

Aquí, hemos recreado nuestro comparador personalizado que ordena las claves por su valor de secciones anteriores. Ahora, las claves String se ordenarán por su longitud en lugar de su valor lexicográfico:

1
2
3
4
5
6
Adam=35
John=21
Mark=31
Aaron=22
Maria=34
Sydney=24

Por supuesto, puede cambiar fácilmente del orden ascendente al orden descendente simplemente agregando un - delante de e.getKey().length():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Map<String, Integer> sortedMap = unsortedMap.entrySet().stream()
        .sorted(Comparator.comparing(e -> -e.getKey().length()))
        .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
5
6
Sydney=24
Aaron=22
Maria=34
Adam=35
John=21
Mark=31

Además, puede usar otros comparadores, como Comparator.comparingInt() si está tratando con valores enteros (aunque estamos aquí, un comparador general también funciona), Comparator.comparingDouble() o Comparator .comparingLong() para satisfacer sus necesidades.

Conclusión

En este tutorial, hemos repasado cómo ordenar un HashMap de Java por clave. Inicialmente usamos un TreeMap para ordenar y mantener el orden de las entradas ordenadas, tanto usando el comparador predeterminado como el personalizado.

Luego, tenemos Java 8 Streams con la clase LinkedHashMap para lograr esta funcionalidad también, tanto para comparadores predeterminados como personalizados en orden ascendente y descendente.

Licensed under CC BY-NC-SA 4.0