Sunday, September 10, 2017

Java 8 - Sorting HashMap by values in ascending and descending order

In the last article, I have shown you how to sort a Map in Java 8 by keys and today, I'll teach you how to sort a Map by values using Java 8 features e.g. lambda expression, method reference, streams, and new methods added into the java.util.Comparator and Map.Entry classes. In order to sort any Map e.g. HashMap, Hashtable, LinkedHashMap, TreemMap, or even ConcurrentHashMap, you can first get set of entries by using the entrySet() method and then you can get the stream by calling the stream() method. The entrySet()  method returns a Set which inherit the stream() method from the java.util.Collection class. Once you got the stream, you can just call the sorted() method which can sort all Map.Entry objects available in Stream using a Comparator.

In order to compare entries of a Map by values, you can use the newly added Map.Entry.comparingByValue() method from the java.util.Map.Entry class.

This is the counterpart of comparingByKey() method which we have used in the last article. Both of these methods are overloaded to work with both Comparable and Comparator objects.

Once you sort the stream, you can do whatever you want to do e.g. if you just want to print keys, values, or entries in sorted order, just use the forEach() method or if you want a Map which is sorted on values then you can use the collect() method of stream class.

This method accepts a Collector and allows you to capture all elements of Stream into whatever collection you want to. For example, if you want a sorted map then you can use the toMap() method of class.

This method is overloaded and provides a couple of choices, for example, you can collect entries in any kind of map or you can also specify the kind of map you want e.g. for keep entries in sorted order, we'll use the LinkedHashMap. It also allows you to break ties in case of same values e.g. you can arrange them in the order you want to.

Btw, If you are curious, you can also see the Pluralsight's From Collections to Streams in Java 8 Using Lambda Expressions course to learn more about new features of Java 8, which is specific to collection framework.

Java 8 - Sorting HashMap by values in ascending and descending order

In short, here are the exact steps to sort a HashMap in Java 8 by values in ascending or descending order, assuming you already have a map object
  1. Get the set of entries by calling the Map.entrySet() method
  2. Get the stream of entries by calling stream() method
  3. Call the sorted method with a Comparator
  4. Use the Map.Entry.comparingByValue() comparator to sort entries by values
  5. Collect the result using the collect() method
  6. Use Collectors.toMap() method to get the result in another Map. 
  7. Provide LinkedHashMap::new to the last parameter to force it to return a LinkedHashMap, to keep the sorted order preserved
  8. In order to sort in decreasing order, just reverse the order of Comparator using Collections.reverseOrder() or Comparator.reverse() method of Java 8.  See Java SE 8 for Really Impatient for the full list of new methods added into key Java classes e.g. Java Collection Framework, String, and Comparator etc. 
Once you follow this step you will get a Map which is sorted by values. Now that you know the theory and steps, let's see the code example in next section to get it right.

Java Program to sort a Map by values

Here is our complete Java program to sort a Map by values using Java 8 features e.g. new methods on existing classes in Java 8 by evolving them using default methods and static methods on interfaces. In this example, I have got a Map of the map of items and their expenses e.g. rent, utility, transportation etc. The Map key is String, which represents item and value is Integer, which is expenses. Our task is to sort the Map by values to find out which item cost us most and print all items in their decreasing order of values.

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

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

 * Java Program to sort a Map by values in Java 8
public class Main {

  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 values first
    Map<String, Integer> sorted = budget
            toMap(e -> e.getKey(), e -> e.getValue(), (e1, e2) -> e2,

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

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

    // now let's sort the map in decreasing order of value
    sorted = budget
            toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e2,

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


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

You can see that before sorting the map has a random order in terms of values but first, we have sorted them in the increasing order of values and later we have sorted the same Map in the decreasing order of values, that's why rent comes first because it cost us highest.

Some tips
1) Use static import to shorten the code, you can static import both Map.Entry and Collectors class.
2) Use method reference in place of lambda expression wherever you can. See this article to learn more about how to convert lambda expression to method reference in Java 8, if you are not familiar with that.

That's all about how to sort a Map by values in Java 8. You can see that it's so easy to sort the Map using new methods added to existing classes. All that is possible because of default method feature of JDK 8, which allows you to add new methods to existing classes. Before this enhancement, it wasn't possible in Java without breaking the existing client of interfaces because as soon as you add a new method to an interface, all its clients had to implement it. This is not required anymore if the method is default or static because they are not abstract but concrete methods.

Further Reading
What's New in Java 8
Java SE 8 for Really Impatient
From Collections to Streams in Java 8 Using Lambda Expressions

Related Java 8 Tutorials
If you are interested in learning more about new features of Java 8, here are my earlier articles covering some of the important concepts of Java 8:
  • 5 Books to Learn Java 8 from Scratch (books)
  • What is default method in Java 8? (example)
  • How to join String in Java 8 (example)
  • How to use filter() method in Java 8 (tutorial)
  • How to format/parse the date with LocalDateTime in Java 8? (tutorial)
  • How to use Stream class in Java 8 (tutorial)
  • How to convert List to Map in Java 8 (solution)
  • Difference between abstract class and interface in Java 8? (answer)
  • 20 Examples of Date and Time in Java 8 (tutorial)
  • How to use peek() method in Java 8 (example)
  • How to sort the map by keys in Java 8? (example)
  • How to sort the may by values in Java 8? (example)
  • 10 examples of Optionals in Java 8? (example)

Thanks for reading this article so far. If you like this article then please share with your friends and colleagues. If you have any questions or suggestions then please drop a comment.

No comments :

Post a Comment