Thursday, May 30, 2024

How to sort a HashMap in Java 8 by keys and values using Stream and Lambda Expressions

Technically, you cannot sort a HashMap in Java because it doesn't guarantee any order. So, even if you add entries in sorted order they will be placed randomly and you won't be able to iterate over map in sorted order of keys or values. Though, if you want to process keys, values or entries of an HashMap in sorted order, you can get the data from the HashMap and then potentially use a TreeMap (when you want to sort a HashMap by key) or LinkedHashMap (when you want to sort a HashMap by value) to store entries in sorted order. This is also the way, we used to sort HashMap before Java 8. 

Now, from Java 8, the process has become much simpler with the introduction of Stream and Collector. The Stream class allows you to process entries of Map, sort it on the fly and Collector will collect them with appropriate Map implementation e.g. TreeMap or LinkedHashMap to give you a sorted Map in Java 8.

For example, you can sort a HashMap in Java 8 by keys in just three lines of code as shown below:

Map<K, V> sortedMap = map.entrySet()
                         .stream()
                         .sorted(Map.Entry.comparingByKey())
                         .collect(Collectors.toMap(
                             Map.Entry::getKey,
                             Map.Entry::getValue,
                             (oldValue, newValue) -> oldValue,
                             LinkedHashMap::new
                         ));

So, what's happening here? let's try to understand above example step by step. In first step we get a set of entries from HashMap by using entrySet() method. We need this because stream() method is not defined for Map, it is defined in Collection class so we first convert Map to Collection and then call the stream() method.


Once you have Stream of entries, we called the sorted() method of Stream class. This method can sort in natural order or custom order. In order to sort the entries by values, we pass a built-in comparator Map.Entry.comparingByValue(), newly added in JDK 8. 

If you look at the Map.Entry inner class, you will find a couple of comparingByKey() and comparingByValue() method which allow you to compare keys and values in their natural and any custom order supplied by external Comparator.

If you are using Generics, make sure you provide type information here otherwise Sorted Method will return entries of Object and all type information will be lost. Don't forget to put the dot after Map.Entry before putting angle bracket.

After this you have sorted entries, now to convert the Stream to Map, we have used collect() method which collect the results. This method accept a Collector and you can pass several built-in collectors by using Collectors utility class. 

The toMap() method collect the result of Stream in Map. It has several overloaded toMap method, so make sure you use the right one for job.

For example,
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
 (e1, e2) - e1))

This method accept key, value and third parameter is to decide which entry to keep if there is a duplicate. This method can result any type of Map even HashMap which doesn't preserve ordering. It totally depends upon implementation.


Better way to collect the sorted entries from Stream into a sorted Map is by using following version of toMap() which allows you to specify which type of Map should be used to store sorted entries.

Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
 (e1, e2) - e1, LinkedHashMap::new)

Now, let's see different ways to sort a HashMap in Java by keys and values:


1. Sorting HashMap in natural order of keys:

Map<K, V> sortedMap = map.entrySet()
                         .stream()
                         .sorted(Map.Entry.comparingByKey())
                         .collect(Collectors.toMap(
                             Map.Entry::getKey,
                             Map.Entry::getValue,
                             (oldValue, newValue) -> oldValue,
                             LinkedHashMap::new
                         ));


2. Sorting Map in reverse order of keys:
Map<K, V> sortedMap = map.entrySet()
                         .stream()
.sorted(Map.Entry.<K, V>comparingByKey(Comparator.reverseOrder()))
                         .collect(Collectors.toMap(
                             Map.Entry::getKey,
                             Map.Entry::getValue,
                             (oldValue, newValue) -> oldValue,
                             LinkedHashMap::new
                         ));

3. Sorting HashMap by keys using Comparator:

Map<String, Integer> sortedMap = map.entrySet()
                                    .stream()
       .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder()))
        collect(Collectors.toMap(
           Map.Entry::getKey,
           Map.Entry::getValue,
           (oldValue, newValue) -> oldValue,
            LinkedHashMap::new
       ));


3. Sorting HashMap in natural order of values:

Map<String, Integer> sortedMap = map.entrySet()
                        .stream()
                        .sorted(Map.Entry.comparingByValue())
                        .collect(Collectors.toMap(
                                Map.Entry::getKey,
                                Map.Entry::getValue,
                                  (oldValue, newValue) -> oldValue,
                                   LinkedHashMap::new
                         ));

4. Sorting Map in reverse order of values:

Map<K, V> sortedMap = map.entrySet()
                         .stream()
.sorted(Map.Entry.<K, V>comparingByValue(Comparator.reverseOrder()))
                         .collect(Collectors.toMap(
                             Map.Entry::getKey,
                             Map.Entry::getValue,
                             (oldValue, newValue) -> oldValue,
                             LinkedHashMap::new
                         ));


5. Sorting HashMap by values using Comparator :

Map<K, V> sortedMap = map.entrySet()
                         .stream()
                         .sorted(Map.Entry.comparingByValue(comparator))
                         .collect(Collectors.toMap(
                             Map.Entry::getKey,
                             Map.Entry::getValue,
                             (oldValue, newValue) -> oldValue,
                             LinkedHashMap::new
                         ));


Important points

Now, let's see important points you should remember when it comes to sorting HashMap using Stream and Lambda Expression in Java 8 onwards:

1) Technically, you cannot sort a Map in Java. It doesn't guaranteed ordering and so is the HashMap or Hashtable, they don't allow you to keep the entries in sorted order, hence it's not possible to sort them.

2) Sometime, you would think that HashMap keep your entries in sorted order especially when you a integer key, as shown below. I would say, don't fool by this behavior. This is just an implementation advantage as public API doesn't guarantee any ordering or sorting in HashMap. 

If your code use this behavior and tomorrow you upgrade to new JDK/JRE version and implementation of HashMap changed, your application will not work as expected and you would spend countless hours figuring out what is wrong. In short, never depends upon implementation specific while coding.

3) If you want to keep your Map sorted then you have two choices, either use TreeMap or LinkedHashMap. The TreeMap keeps entries in sorted order of key, hence key must implement Comparable method. 

In that case, TreeMap will sort entries in natural order of keys. Alternatively, you can supply a Comparator to sort the keys in the order you want, but bottom line is that entries will always be sorted by keys and not values.

4) The LinkedHashMp provides a List kind of ordering i.e. it keeps the entries in the order they are inserted into Map. 

This gives you flexiblity to sort the Map by keys or values. All you need to do is insert the entries in the order you want it could be by keys or by values. 

As shown in earlier examples, I personally use LinkedHashMap to sort the HashMap by values in Java.


5) When you first start coding in Java 8 and try to do your day-to-day stuff using Java 8 way e.g. by using lambda expressions, streams, and method references, you will face some challenges. Don't discouraged by that, just pay a closer attention to the code which is working and try to figure out why your code is not working.

For example, by default sorted() method of Stream() will return a Map of Object and Object until you provide generic information, but it's tricky if you don't know how to do it. Even myself struggled, until I was able to spot a dot which separates angle bracket of generics with class.

8) First try to code using lambda expression then shorten it by using method references.

9) Collector has several overloaded toMap() method so you must pay attention which version you are using. By default toMap() will chose a Map implementation by itself, but there are variants which allow you specify a particular Map implementation e.g. LinkedHashMap when you are trying to sort the Map by values.

For example,  
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) - e1)

can return any Map implementation, possible a HashMap which will lost the sorting, but

Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
 (e1, e2) - e1, LinkedHashMap::new)

will return a LinkedHashMap which will preserver sorting order.

Here is also a nice memory map of sorting HashMap in Java by keys and values:

How to sort a HashMap in Java 8 using Stream and Lambda Expressions



That's all about how to sort a HashMap in Java 8. We have seen examples of sorting a HashMap by keys, values and entries by using Java 8 techniques e.g. stream, lambda expressions, method reference, and new API methods provided by JDK to facilitate sorting of Map e.g. comparingByKey() and comparingByValue() method of Map.Entry class.

You should also try to first write code using lambda expression and then shorten by using method references for better readability. 

Also, remember to use correct toMap() method from Collectors class because if you don't provide the implementation information, toMap() can use any implementation which can potentially lose the ordering you achieved by sorting HashMap by keys or values.

Other Core Java tutorials you may like to explore:
  • How to use Stream class in Java 8 (tutorial)
  • 6 Advanced Comparator and Comparable Examples (advanced sorting)
  • Difference between abstract class and interface in Java 8? (answer)
  • 5 Books to Learn Java 8 from Scratch (books)
  • 20 Examples of Date and Time in Java 8 (tutorial)
  • 5 Free Courses to learn Java 8 and 9 (courses)
  • How to sort the map by keys in Java 8? (example)
  • How to convert List to Map in Java 8 (solution)
  • How to use peek() method in Java 8 (example)
  • How to use filter() method in Java 8 (tutorial)
  • How to sort the may by values in Java 8? (example)
  • How to join String in Java 8 (example)
  • 10 Example of Comparator in Java? (Comparator example)
All the best with your Java learning journey !!

2 comments:

  1. Why do you ever need to sort an HashMap if its not supposed to be sorted?

    ReplyDelete
    Replies
    1. Understand your point but HashMap is popular out there, you run into them everywhere and all of a sudden you might need to sort them by keys or values ;)

      Delete