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

In past, I have shown you how to sort a HashMap by values in Java, but that was using traditional techniques of pre-Java 8 world. Now the time has changed and Java has evolved into a programming language which also has the power of functional programming. How can you take advantage of that fact to do your day to day task e.g. 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.

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 existing interface e.g. Comparator, Map.Entry which makes day-to-day coding much easier.

This evaluation of existing interfaces was possible by introducing non-abstract method on interface e.g. default methods and static methods.

Because of this path breaking feature, it's possible to add new methods into 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.

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 sorted() method of Stream to sort an HashMap by values in Java.




Sorting Map by values on Increasing order

You can sort a Map e.g. an HashMap, LinkedHashMap, or TreeMap in Java 8 by using the sorted() method of java.util.stream.Stream class. These 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 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 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));

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 reference in Java 8. You can read more about that in any good Java 8 book e.g. Java 8 in Action or Java SE 8 for Really Impatient by Cay S. Horstmann.



Sorting Map by values on decreasing Order

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 reversed() method, rest of the code is 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 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 we just use Class::new, that's it.




Important points to Remember

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 book e.g. Java SE 8 for Really Impatient by Cay S. Horstmann.



Java Program to Sort the Map 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 e.g. 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 map is sorted 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 example, we rectified our mistake and collected the result of the sorted stream into a LinkedHashMap, hence we have entries in sorted order. 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 e.g. 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 collect() method to store sorted entries into another Map. Also, when you use the Collector to collect element from sorted Stream, make sure you use LinkedHashMap to collect the result, otherwise ordering will be lost.


Further Reading
What's New in Java 8
Java SE 8 for Really Impatient


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:
  • 20 Examples of Date and Time in Java 8 (tutorial)
  • How to join String in Java 8 (example)
  • How to use filter() method in Java 8 (tutorial)
  • 5 Books to Learn Java 8 from Scratch (books)
  • How to use Stream class in Java 8 (tutorial)
  • How to use forEach() method in Java 8 (example)
  • How to convert List to Map in Java 8 (solution)
  • How to use peek() method in Java 8 (example)

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

No comments:

Post a Comment