Sort Map by Value in Java
There are several direct ways to deal with the order of the keys in a map because keys must be unique. However, there isn't any direct method available in Java yet for sorting based on values. So, we have discussed different ways by which a Map can be sorted by its values. We'll use Stream API's sorted() method, Collections.sort(), and Comparator interface to enable sorting based on values.
Introduction
It isn't easy to deal with the values of a map in Java. There might be some guidelines to follow while assigning a key to any key-value pair. However, the same doesn't apply to the corresponding values. The reason is obvious: there needs to be a unique key to fetch the values, but there can be the same for two unique keys.
It can sometimes be puzzling to obtain a map sorted according to values. Moreover, there's no such sort() method for a map. The ** Collections. sort () ** method is only applicable for lists such as ** ArrayList **, ** LinkedList **, etc. So, the discomfort brings us back to creating our own way of sorting a map by value in Java.
Upon a closer look, it's clear that a list can be used to facilitate the sorting of the map's elements with the help of Collections.sort(). We can also use a comparator at times to sort the values in a map. Although we do have access to TreeMap, a TreeMap makes a comparison to sort elements after every modification, be it insertion, deletion or change in any value. This makes the TreeMap slower than the HashMap.
Maps | HashMap | TreeMap |
---|---|---|
put | O(1) | O(logN) |
remove | O(1) | O(logN) |
get | O(1) | O(logN) |
containsKey | O(1) | O(logN) |
Data Structure | Hashtable | Red-Black Tree |
Let us consider an example where we have a map of fruits as keys with the prices mentioned as their values. Now, we need to sort all the fruits as per their price value. So, we have the following approach to deal with this challenge.
Example: Sort Map by Value in Java Using sort() Method
The basic idea for sorting a map by value in Java is to use what’s already available: the sort() method for lists.
First of all, lambda expressions will be used along with forEach loop to avoid multiple lines, specifying the entire for loop with the print statement.
A lambda expression is an instance of a functional interface (interface comprising only one abstract method). It is an anonymous method that provides the implementation for the abstract method. Lambda expressions are translated into byte code during the compilation process.
Notice that the parameters are mentioned inside the braces. The surrounding context of the expression in Java reduces the need for type declarations of these parameters. It is followed by a lambda operator (->), followed by the abstract method's implementation. This implementation will be generated with a private method or private static method during compilation. Moreover, the forEach loop is simply used to iterate over the map's elements one by one.
Returning to our example, we must create a list from the entrySet of the Map.
Since we are dealing with an entry of a Map, we'll specify that the list accepts the entry being converted to the generic Map.Entry, which makes the accessing of elements (entry) from a Map more convenient due to the availability of several methods.
The Map.Entry interface comprises the comparingByValue() method, which in turn, compares Map. Entry is based on the values and follows the natural sorting order. Alternatively, it also uses the user-defined Comparator to compare the values in a map.
Inside the sort() method, pass the comparingByValue() method to enable comparison among the values of the entry.
Note: The method comparingByValue() throws NullPointerException while comparing any entry having null value.
Therefore, the list eventually consists of the elements sorted according to their values.
Output:
Time Complexity:
Example: Sort Map by Value in Java using Stream APIs (In ascending and descending order)
Sorting in Ascending Order
The second option is to use Stream API to sort the map by values. We'll use it for ascending and descending order.
Several methods present in Stream API can be used to process a collection of objects. A stream is a collection of objects backed by different pipelined methods.
The intermediate operations in Stream API return a stream object, which is then processed by the terminal operations. Here, we will use the sorted() method for stream objects.
So, we use the stream() method to convert the entrySet of the Map into the stream and apply the sorted() method to them, which will sort the map.
Also, the sorted method is informed of the natural sorting order with the help of Map.Entry comparingByValue() method. With forEach, we can access all the elements to get the key-value pairs individually.
Output:
Time Complexity:
Sorting in Descending Order
Everything here is the same as we did for the ascending order, except for comparingByValue().reversed() method that will follow the reverse ordering of the elements.
Output:
Time Complexity:
Note: We haven't stored the sorted elements anywhere using Stream API yet, so the ordering isn't maintained once we have accessed each element through forEach loop. The hashmap will remain unsorted after the process. To deal with this problem, we'll be using collect() method in further examples.
Example: Sort Map by Value in Java Using sort() Method with Comparator
The idea is to create a list (LinkedList in this case) to perform our sorting operation but also with a comparator that accepts an entry from a map and internally uses compare() method to compare the entries based on their respective values.
Then, we need to maintain the insertion order of the elements from now on. So, we'll use LinkedHashMap to insert the elements one by one from the LinkedList.
LinkedHashMap is exactly the same as a HashMap but LinkedHashMap also has LinkedList along with Hashtable as its data structure. Therefore, unlike HashMap, it can preserve the insertion order.
Output
Time Complexity:
Example: Sort Map by Value in Java Using collect() and toMap() Method
Initially, we need to know a bit about the Collector interface. It consists of various reduction operations by which we can gather the elements in a container. The toMap() method present in the Collector interface accumulates the elements in a Map. There are 3 different ways to specify overloading on toMap() method, out of which we are going to use the following one:
Syntax:
Here, T refers to the type of the input elements. toMap() comprises four different parameters.
- keyMapper generates all the keys
- valueMapper generates all the values
- mergeFunction will be used at places where different values have been assigned the same keys in order to avoid collision
- mapSupplier is a new empty Map passed as to insert the elements from the original map. In our case, we'll be using LinkedHashMap to store the elements with their insertion order.
Note: We have also used constructor reference (::) to provide direct reference to the constructor. Example:
Finally, the collect() method is a terminal operation that collects the stream elements which we can use forEach loop to print the elements.
Output:
Time Complexity:
Example: Sort Map by Value in Java using Custom Code
We can create our own Comparator class of Object type that has a map as its instance variable. We also need to define a constructor with a map as a parameter.
The compare() method will compare the two objects as per their mapped values. The sorting operation depends on the value that the compare() method returns.
Output:
Time Complexity:
Conclusion
- We can create a list to sort the elements of a map and use Map.Entry.comparingByValue() method to sort them as per the natural sorting order.
- Stream API can be implemented to sort maps in Java either in ascending order with stream().sorted() that uses comparingbyValue() as a parameter.
- For descending order, stream().sorted() will be used along with comparingbyValue().reversed() as a parameter.
- We can also make use of the Comparator interface to implement compare method for the sorting operation.
- The Collector interface constitutes the collect method to accumulate the stream of objects, and toMap() method can be used to convert the collection into a Map.
- TreeMap can also be used with the user-defined Comparator class. However, since the sorting happens with every modification in the map, TreeMap becomes slower than HashMap.