Thursday, September 21, 2023

How to sort HashMap by values in Java 8 [using Lambdas and Stream] - Example Tutorial

In the past, I have shown you how to sort a HashMap by values in Java, but that was using traditional techniques of the pre-Java 8 world. Now the time has changed and Java has evolved into a programming language that can also do functional programming. How can you, a Java Programmer take advantage of that fact to do your day-to-day task better like how do you sort a Map by values in Java using lambda expressions and Stream API. That's what you are going to learn in this article. It will serve two purposes, first, it will tell you a new way to sort a Map by values in Java, and, second and more important it will introduce you to essential Java 8 features like Lambda Expression and Streams, which every Java Programmer should learn.

By the way, it's not just the lambda expression and stream which makes coding fun in Java 8, but also all the new API methods added into an existing interface like Comparator, Map.Entry makes day-to-day coding much easier.

This evaluation of existing interfaces was possible by introducing the non-abstract method on interfaces like default methods and static methods.

Because of this path-breaking feature, it's possible to add new methods into the existing Java interface and Java API designers have taken advantage to add much-needed methods on popular existing interfaces.

One of the best examples of this is java.util.Comparator interface which has now got comparing() and thenComparing() methods to chain multiple comparators, making it easier to compare an object by multiple fields, which was very tedious and requires a lot of nesting prior to Java 8.

The Map.Entry class, which is a nested static class of java.util.Map interface is also not behind, it has got two additional methods comparingByKey() and comparingByValue() which can be used to sort a Map by key and values. They can be used along with the sorted() method of Stream to sort a HashMap by values in Java.

Btw, if you are new to the Java world then I suggest you start learning from Java 8 itself, no need to learn the old techniques of doing a common task like sorting a list or map, working with date and time, etc and if you need some help, you can also look at comprehensive online Java courses like The Complete Java MasterClass, which will not only teach you all this but much more. 




How to Sort a Map by values in Increasing order in Java

You can sort a Map like a HashMap, LinkedHashMap, or TreeMap in Java 8 by using the sorted() method of java.util.stream.Stream class. This means accepts a Comparator, which can be used for sorting. If you want to sort by values then you can simply use the comparingByValue() method of the Map.Entry class

This method is newly added in Java 8 to make it easier for sorting.

ItemToPrice.entrySet()
.stream()
.sorted(Map.Entry.<String, Integer>comparingByValue())
.forEach(System.out::println);

Btw, if you need a Map instead of just printing the value into the console, you can collect the result of the sorted stream using the collect() method of Stream and Collectors class of Java 8 as shown below:

// now, let's collect the sorted entries in Map
Map<String, Integer> sortedByPrice = ItemToPrice.entrySet()
.stream()
.sorted(Map.Entry.<String, Integer>comparingByValue())
.collect(Collectors.toMap(e -> e.getKey(),e -> e.getValue()));

The Map returned by the previous statement was not sorted because the order was lost while collecting results in Map you need to use the LinkedHashMap to preserve the order

Map<String, Integer> sortedByValue = ItemToPrice.entrySet()
.stream()
.sorted(Map.Entry.<String, Integer>comparingByValue())
.collect(toMap(Map.Entry::getKey,
               Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));

This is the right way to sort a Map by values in Java 8 because now the ordering will not be lost as Collector is using LinkedHashMap to store entries. This is also a good example of using constructor references in Java 8. You can read more about that in the Collections to Streams in Java 8 Using the Lambda Expressions course on Pluralsight, which provides an in-depth explanation of new Java 8 features.

How to sort HashMap by values in Java 8





Sorting a Map by values on decreasing Order in Java

In order to sort a Map by values in decreasing order, we just need to pass a Comparator which sort it in the reverse order. You can use the reversed() method of java.util.Comparator purpose to reverse order of a Comparator. 

This method is also newly added in the Comparator class in JDK 8.

Map<String, Integer> sortedByValueDesc = ItemToPrice.entrySet()
.stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.collect(toMap(Map.Entry::getKey, 
               Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));

The key point here is the use of the reversed() method, the rest of the code is the same as the previous example. In the first step, you get the entry set from the Map, then you get the stream, then you sorted elements of the stream using the sorted() method, which needs a comparator.

You supply a Comparator which compares by values and then reversed it so that entries will be ordered in the decreasing order. 

Finally, you collected all elements into a Map and you asked Collector to use the LinkedHashMap by using constructor reference, which is similar to method reference in Java 8 but instead of using method name, it uses Class::new, that's it. If you are interested, you can learn about it in any good Java 8 book like Java 8 in Action by Raul Gabriela Ulma on Manning publication. 

Sort HashMap by values in Java 8 using Lambdas and Stream




Important points about HashMap and Map in Java

Here are some of the important points to remember while sorting a Map by values in Java 8. These are very important for correctly sorting any HashMap or Hashtable as well:
  1. Use LinkedHashMap for collecting the result to keep the sorting intact.

  2. Use static import for better readability e.g. static import Map.Entry nested class.

  3. Use new comparingByKey() and comparingByValue() method from Map.Entry they were added in Java 8 to make sorting by key and value easier in Java.
     
  4. Use reversed() method to sort the Map in descending order

  5. Use forEach() to print the Map

  6. Use Collectors to collect the result into a Map but always use LinkedHashMap because it maintains the insertion order. 
You can learn more about lambda expression and method reference used in our example in a good Java 8 course like The Complete Java MasterClass on Udemy.

How to Sort Map by values in Java 8 using Lambdas and Stream - Example Tutorial




Java Program to Sort an HashMap by Values in JDK 8

Here is our complete Java program to sort a HashMap by values in Java 8 using a lambda expression, method reference, and new methods introduced in JDK 8 like Map.Entry.comparingByValue() method, which makes it easier to sort the Map by values.

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package test;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.*;

/**
 *
 * @author Javin Paul
 */
public class SortingMapByValueInJava8 {

  /**
   * @param args
   * the command line arguments
   */
  public static void main(String[] args) {

    // Creating a Map with electoric items and prices
    Map<String, Integer> ItemToPrice = new HashMap<>();
    ItemToPrice.put("Sony Braiva", 1000);
    ItemToPrice.put("Apple iPhone 6S", 1200);
    ItemToPrice.put("HP Laptop", 700);
    ItemToPrice.put("Acer HD Monitor", 139);
    ItemToPrice.put("Samsung Galaxy", 800);

    System.out.println("unsorted Map: " + ItemToPrice);

    // sorting Map by values in ascending order, price here
    ItemToPrice.entrySet().stream()
        .sorted(Map.Entry.<String, Integer> comparingByValue())
        .forEach(System.out::println);

    // now, let's collect the sorted entries in Map
    Map<String, Integer> sortedByPrice = ItemToPrice.entrySet().stream()
        .sorted(Map.Entry.<String, Integer> comparingByValue())
        .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));

    System.out.println("Map incorrectly sorted by value in ascending order: "
        + sortedByPrice);

    // the Map returned by the previous statement was not sorted
    // because ordering was lost while collecting result in Map
    // you need to use the LinkedHashMap to preserve the order

    Map<String, Integer> sortedByValue = ItemToPrice
        .entrySet()
        .stream()
        .sorted(Map.Entry.<String, Integer> comparingByValue())
        .collect(
            toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1,
                LinkedHashMap::new));

    System.out.println("Map sorted by value in increasing order: "
        + sortedByValue);

    // sorting a Map by values in descending order
    // just reverse the comparator sorting by using reversed() method
    Map<String, Integer> sortedByValueDesc = ItemToPrice
        .entrySet()
        .stream()
        .sorted(Map.Entry.<String, Integer> comparingByValue().reversed())
        .collect(
            toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1,
                LinkedHashMap::new));

    System.out.println("Map sorted by value in descending order: "
        + sortedByValueDesc);
  }

}
Output
unsorted Map: {Samsung Galaxy=800, HP Laptop=700, Sony Braiva=1000,
               Acer HD Monitor=139, Apple iPhone 6S=1200}
Acer HD Monitor=139
HP Laptop=700
Samsung Galaxy=800
Sony Braiva=1000
Apple iPhone 6S=1200
Map incorrectly sorted by value in ascending order: 
{Samsung Galaxy=800, HP Laptop=700, Sony Braiva=1000, 
Acer HD Monitor=139, Apple iPhone 6S=1200}
Map sorted by value in increasing order: 
{Acer HD Monitor=139, HP Laptop=700, Samsung Galaxy=800, 
Sony Braiva=1000, Apple iPhone 6S=1200}
Map sorted by value in descending order: {Apple iPhone 6S=1200,
 Sony Braiva=1000, Samsung Galaxy=800, HP Laptop=700, Acer HD Monitor=139}


You can see that the map is sorted now by values, which are integers. In this first example, we have printed all entries in sorted order and that's why Acer HD Monitor comes first because it is least expensive, while Apple iPhone comes last because it is most expensive.

In the second example, even though we sorted in the same way as before, the end result is not what you have expected because we failed to collect the result into a Map which keeps them in the order they were i.e. we should have used LinkedHashMap, which keeps entries in the order they were inserted.

In the third and fourth examples, we rectified our mistake and collected the result of the sorted stream into a LinkedHashMap, hence we have entries in sorted order. In the last example, sort entries in descending order hence, Apple comes first and Acer comes last.

Here is a one-liner in Java 8 to sort a HashMap by values:

How to Sort Map by values in Java 8 using Lambdas and Stream


That's all about how to sort a Map by values in Java 8. you can use this technique to sort any Map implementations like HashMap, Hashtable, ConcurrentHashMap, TreeMap, etc. If you don't need to print the values or perform any operation, but you just need a sorted Map then make sure you use the collect() method to store sorted entries into another Map. 

Also, when you use the Collector to collect elements from sorted Stream, make sure you use LinkedHashMap to collect the result, otherwise ordering will be lost.



Other Java 8 Tutorials you may like
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:
  • Top 5 Courses to Learn Java 8 Programming (courses)
  • 5 Books to Learn Java 8 from Scratch (books)
  • How to join String in Java 8 (example)
  • How to use filter() method in Java 8 (tutorial)
  • Java 8 map + filter + stream example (tutorial)
  • How to use Stream class in Java 8 (tutorial)
  • 5 Free Java 8 and  Java 9 courses for Programmers (courses)
  • How to use forEach() method in Java 8 (example)
  • How to convert List to Map in Java 8 (solution)
  • 20 Examples of Date and Time in Java 8 (tutorial)
  • How to format/parse the date with LocalDateTime in Java 8? (tutorial)
  • How to use peek() method in Java 8 (example)
  • How to sort the map by keys in Java 8? (example)
  • 10 examples of Options in Java 8? (example)
  • Top 5 websites to learn Java Coding for FREE (websites)

Thanks for reading this tutorial so far. If you like this example of sorting HashMap in Java 8 using lambda expression then please share it with your friends and colleagues. If you have any questions, feedback, or suggestion then please drop a comment.

P. S. - If your goal is to learn new features introduced in Java 8 then you can also check out the free Java 8 courses which only focus on new features and nothing else.

Now, is the quiz time, what is difference between HashMap and LinkedHashMap in Java? Can you pass HashMap to  a method which expect a TreeMap a LinkedHashMap?

10 comments:

  1. What's the time complexity of this sort?

    ReplyDelete
  2. If you only need a few top elements:

    map.entrySet().stream()
    .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
    .limit(count)
    .collect(toMap(Map.Entry::getKey, Map.Entry::getValue,
    (e1, e2) -> e1,
    LinkedHashMap::new))

    ReplyDelete
    Replies
    1. This is good one, thx for sharing this useful code with us Kisna, appreciate it.

      Delete
  3. toMap not working while running

    ReplyDelete
  4. We need to use .collect(Collectors.
    toMap(Map.Entry::getKey,Map.Entry::getValue,(e1,e2)->e1,
    LinkedHashMap::new))

    ReplyDelete
  5. I m getting an error ..comparator and stream are not found even after importing also

    ReplyDelete
    Replies
    1. Can you post your code and the error you are getting here, I will try to compile at my end

      Delete

Feel free to comment, ask questions if you have any doubt.