Tuesday, November 2, 2021

How to sort a Map by keys in Java 8 - Example Tutorial

In the last article, I have shown you how to sort a Map by values in Java 8, and in this tutorial, you will learn how to sort a Map by keys like a HashMap, ConcurrentHashMap, LinkedHashmap, or even Hashtable. Theoretically, you cannot sort a Map because it doesn't provide any ordering guarantee. For example, when you iterate over a HashMap, you don't know in which order entries will be traversed because HashMap doesn't provide any ordering. Then, how can you sort a Map which doesn't support order? Well, you can't and that's why you only sort entries of HashMap but you don't store the result back into HasMap or any other Map which doesn't support ordering. If you do so, then sorting will be lost.

Here is an example of incorrect sorting. Here even after sorting the Map, we are doing the mistake of storing the result back into a Map that doesn't provide any ordering guarantee, hence the result is an unordered map even after sorting.

Map sorted = budget
.entrySet()
.stream()
.sorted(comparingByKey())
.collect(toMap(e -> e.getKey(), e -> e.getValue(), (e1, e2) -> e2));

Here is the output to confirm what I said:
map before sorting: {grocery=150, utility=130, miscellneous=90,
 rent=1150, clothes=120, transportation=100}
map after sorting by keys: {grocery=150, utility=130, miscellneous=90,
 rent=1150, clothes=120, transportation=100}

If Map was sorted then the "clothes" should have come first ahead of "grocery". The mistake was blindly relying on toMap() method of the Collectors class. This class provides no guarantee of what kind of Map will be used to collect those elements. Since Map interface doesn't guarantee order, they are also not bound to store elements in any order.



However, it's easy to solve this problem because the Collectors class also provides an overloaded version of toMap() class which allows you to instruct which kind of Map should be used to store those entries. You can use a LinkedHashMap to store mappings to preserve the sorting order because LinkedHashMap keeps keys in the order they were added. Here is the modified code which sorts a Map in the order of keys:

Map sorted = budget
.entrySet()
.stream()
.sorted(comparingByKey())
.collect(toMap(e -> e.getKey(), e -> e.getValue(),
(e1, e2) -> e2), LinkedHashMap::new));

The code passed into to toMap() method is interesting, the first parameter is used as a key, the second is used as a value and the third is used to break ties i.e. if two entries are equal then which entries will be chosen is decided by the third parameter, here we are using the second entry. 

The fourth parameter is the important one, which uses a constructor reference to tell Collector that copying a LinkedHashMap should be used. 

Btw, if you are not familiar with new Java 8 features like Effectively final variable then I suggest you first go through a comprehensive and up-to-date Java course like The Complete Java MasterClass on Udemy. It's also very affordable and you can buy in just $10 on Udemy sales which happen every now and then.





Steps to sort a Map by keys in Java 8

Here are the high-level steps you can take to sort a Map e.g. HashMap, Hashtable, ConcurentHashMap, or LinkedHashMap to sort them in the ascending and descending order of their keys:

1) Get all entries by calling the Map.entrySet() method

2) Get a stream of entries by calling the stream() method, which Set inherit from the Collection interface.

3) Sort all entries of Stream by calling the sorted() method.

4) In order to sort them by keys, provide a Comparator to a sorted() method that sorts entries by keys. This can be done by calling Map.Entry.comparingKey() method returns a Comparator which compares keys in their natural order.

5) Store the result of sorting in a LinkedHashMap by using the collect() method of Stream class.

6) Use Collectors.toMap() method to collect sorted entries into LinkedHashMap





Java Program to sort a Map by keys in JDK 8

Here is the complete Java program to sort Map e.g. HashMap by keys in JDK 8. In this example, you will learn to sort Map by both lambda expression and method reference. We'll also use new classes e.g. Stream and new methods added into Map.Entry class to sort all entries by their Map and store the result into a LinkedHashMap.

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import static java.util.stream.Collectors.*;
import static java.util.Map.Entry.*;

/*
* Java Program to sort a Map by keys in Java 8
* 
*/
public class Java8Demo{

public static void main(String[] args) throws Exception {

// a Map with string keys and integer values
Map<String, Integer> budget = new HashMap<>();
budget.put("clothes", 120);
budget.put("grocery", 150);
budget.put("transportation", 100);
budget.put("utility", 130);
budget.put("rent", 1150);
budget.put("miscellneous", 90);

System.out.println("map before sorting: " + budget);

// let's sort this map by keys first
Map<String, Integer> sorted = budget
.entrySet()
.stream()
.sorted(comparingByKey())
.collect(
toMap(e -> e.getKey(), e -> e.getValue(),
(e1, e2) -> e2, LinkedHashMap::new));

System.out.println("map after sorting by keys: " + sorted);

// above code can be cleaned a bit by using method reference
sorted = budget
.entrySet()
.stream()
.sorted(comparingByKey())
.collect(
toMap(Map.Entry::getKey, Map.Entry::getValue,
(e1, e2) -> e2, LinkedHashMap::new));


// now let's sort the map in decreasing order of keys
sorted = budget
.entrySet()
.stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByKey()))
.collect(
toMap(Map.Entry::getKey, Map.Entry::getValue,
(e1, e2) -> e2, LinkedHashMap::new));

System.out.println("map after sorting by keys in descending order: " + sorted);
}

}

Output
map before sorting: {grocery=150, utility=130, miscellneous=90, 
       rent=1150, clothes=120, transportation=100}
map after sorting by keys: {clothes=120, grocery=150, miscellneous=90, 
       rent=1150, transportation=100, utility=130}
map after sorting by keys in descending order: {utility=130, 
       transportation=100, rent=1150, miscellneous=90, grocery=150, clothes=120}


You can see that initially map was not sorted but it is later sorted in the order of keys, which are a string and that's why clothes come ahead of the grocery. 

Similarly, when we sorted the map in descending order, clothes come last. This proves that our sorting code is working fine.

If you want more sophistication and customization you can do that at the Comparator level and you can provide additional Comparator to comparingKey() method, which by default compare keys in their natural order.

For example, if a key were not String but a user object e.g. a Book, then you could have sorted the book by title, author, or price by providing the corresponding comparator to comparingKey() method of java.util.Map.Entry class. Both comparingKey() and comparingValue() are overloaded to accept a Comparator. You can see a good Java 8 book like Java SE 8 for Really Impatient book to learn more about them.

How to sort a Map by keys in Java 8


That's all about how to sort a Map by keys in Java 8. The simplest way to achieve this is by using the sorted() method of Stream and the newly added comparingKey() method of Map.Entry class. 

The stream sorts all elements and then depending upon your need, you can either print entries in sorted order or store them in an ordered map, for example, LinkedHashMap, or a sorted map like TreeMap. You can also sort entries in their reverse order by just reversing the Comparator using the Collections.reverseOrder() method or Comparator.reversed() method of Java 8.



Related Java 8 Tutorials
If you are interested in learning more about the new features of Java 8, here are my earlier articles covering some of the important concepts of Java 8:
  • 20 Examples of Date and Time in Java 8 (tutorial)
  • How to use Stream class in Java 8 (tutorial)
  • How to use filter() method in Java 8 (tutorial)
  • How to use forEach() method in Java 8 (example)
  • How to join String in Java 8 (example)
  • How to convert List to Map in Java 8 (solution)
  • How to use peek() method in Java 8 (example)
  • 5 Books to Learn Java 8 from Scratch (books)
Thank you for reading this article so far. If you like this tutorial then please share it with your friends and colleagues. If you have any questions or feedback then please drop a comment.

P.S.: If you want to learn more about new features in Java 8 then please see the tutorial What's New in Java 8. It explains all important features of Java 8 e.g. lambda expressions, streams, functional interface, Optionals, new date and time API, and other miscellaneous changes.

3 comments :

SARAL SAXENA said...

Well would like to go for short approach ,use a TreeMap. This is precisely what its for. If this map is passed to you and you cannot determine the type, then you can do the following:

SortedSet keys = new TreeSet(map.keySet());
for (String key : keys) {
String value = map.get(key);

}

javin paul said...

Hello asdf, thanks, but what do you mean by port to something useful? Can you please elaborate?

Anonymous said...

nice article!

Post a Comment