Map.Entry Interface Enhancements : What’s new in Java 8 to Java 10

1. Introduction

In this article we will discuss 4 methods added in the Entry interface. Entry interface is a nested interface in java.util.Map interface. Map interface defines contract of Hashing based data structures in Java. The hashing based data structures supported accepts a pair of objects as an input known as Key-Value pair. Entry interface defines an entry in data structure, more formally a Key-Value pair. No new methods were added in Map.Entry interface after Java 8 till Java 12.

2. Content

4 new methods added in the Entry interface are Comparator methods. I have written extensively on Comparator interface and utilities provided for Comparators in Spring Framework, Google Guava and Apache Collections. All of this is included in free eBook with over 50 examples. Get the eBook below:

Below are the methods that we will discuss:

public static <K extends Comparable<? super K>, V> 
	Comparator<Map.Entry<K, V>> comparingByKey()

public static <K, V> Comparator<Map.Entry<K, V>> 
	comparingByKey(Comparator<? super K> cmp)

public static <K, V extends Comparable<? super V>> 
	Comparator<Map.Entry<K, V>> comparingByValue()

public static <K, V> Comparator<Map.Entry<K, V>> 
	comparingByValue(Comparator<? super V> cmp)

Let us dig in.

3. public static <K extends Comparable<? super K>, V>
Comparator<Map.Entry<K, V>> comparingByKey()

comparingByKey() method allows the entries in Map to be sorted using Key using natural order comparison. But it is mandatory that the Key class implements Comparable interface. If the Key class doesn’t implement the Comparable interface then you will get compile time warning.

Let us consider the below example : 

Give Map<Long, Transaction> i.e. transactionId : transaction mapping, sort it using transactionId.

Map<Long, Transaction> txns = Transactions.getDataSet();

List<Map.Entry<Long, Transaction>> entries 
	= new ArrayList<>(txns.entrySet());

entries.sort(Map.Entry.comparingByKey());

Input (Transaction id : date : country)
3422499359609435510 2020-07-08 AU
6350466453126494862 2020-06-29 US
6000301675618163226 2020-07-03 CA
4401599011821486997 2020-07-03 US

Output (Transaction id : date : country)
3422499359609435510 2020-07-08 AU
4401599011821486997 2020-07-03 US
6000301675618163226 2020-07-03 CA
6350466453126494862 2020-06-29 US

Let us take a little more complex example. In the above example we copied all entries in ArrayList and sorted it. Instead of that, sort the input Map itself and return the result in another Map. 

Hint : Don’t return in HashMap because keys are hash based. After sorting the result’s order must be preserved. Try using stream().

Map interface doesn’t have a stream() method. But the entrySet() method of the Map interface returns Set<Map.Entry<K, V>> and Set has stream() method. Stream API provides us with the sorted() and collect() method. Both are extremely useful for our solution.

Let me break down.

3.1 Get the stream

Stream<Entry<Long, Transaction>> stream = txns.entrySet().stream();

3.2 Sort the stream using key

Stream<Entry<Long, Transaction>> stream 
			= txns
				.entrySet()
				.stream()
				.sorted(Map.Entry.comparingByKey());

3.3 Collect the result in the order it is sorted

We need to use Collectors.toMap method to insert results in the new Map. We also need to insert the result in LinkedHashMap as it maintains the insertion order. 

Collectors.toMap method has 4 arguments

  1. Key mapper
    1. Input type : Function interface.
    2. We are iterating over Entry objects so we need to extract out the Key from it.
  2. Value mapper
    1. Input type : Function interface.
    2. We are iterating over Entry objects so we need to extract out the Value from it.
  3. Merge Function
    1. Input type : BinaryOperator interface.
    2. If the key collides with another key in the result Map then use this function to resolve the collision.
  4. Map Supplier
    1. Input type : Supplier interface.
    2. Provides an instance of Map that will be used as a container for these entries.

Solution : 

Map<Long, Transaction> txns = Transactions.getDataSet();

Function<Entry<Long, Transaction>, Long> keyMapper 
	= entry -> entry.getKey();
		
Function<Entry<Long, Transaction>, Transaction> valueMapper 
	= entry -> entry.getValue();
		
BinaryOperator<Transaction> mergeFunction = (o1, o2) -> o1;
		
Supplier<LinkedHashMap<Long, Transaction>> mapFactory 
	= LinkedHashMap::new;
		
Map<Long, Transaction> result 
	= txns
		.entrySet()
		.stream()
		.sorted(Map.Entry.comparingByKey())
		.collect(Collectors.toMap(
							keyMapper, 
							valueMapper, 
							mergeFunction, 
							mapFactory));

Input (Transaction id : date : country)
8709724015409291101 2020-06-29 US
8931993394878405942 2020-07-03 CA
754625269741161028 2020-07-03 US
3403173199038627691 2020-07-08 AU

Output (Transaction id : date : country)
754625269741161028 2020-07-03 US
3403173199038627691 2020-07-08 AU
8709724015409291101 2020-06-29 US
8931993394878405942 2020-07-03 CA

4. public static <K, V> Comparator<Map.Entry<K, V>>
comparingByKey(Comparator<? super K> cmp)

comparingByKey(Comparator) is used to provide a custom sorting strategy for the Key.

Below example considers sorting entries in List.

Map<Long, Transaction> txns = Transactions.getDataSet();

List<Entry<Long, Transaction>> entries 
	= new ArrayList<>(txns.entrySet());

Comparator<Entry<Long, Transaction>> comparingByKey 
	= Map.Entry.comparingByKey(Comparator.reverseOrder());

entries.sort(comparingByKey);

Input (Transaction id : date : country)
7177261000415290599 2020-07-08 AU
4358294855140697001 2020-07-03 US
7996179609963446997 2020-06-29 US
7537251389392391656 2020-07-03 CA

Output (Transaction id : date : country)
7996179609963446997 2020-06-29 US
7537251389392391656 2020-07-03 CA
7177261000415290599 2020-07-08 AU
4358294855140697001 2020-07-03 US

Let us now sort using streams API.

Map<Long, Transaction> result 
	= txns
		.entrySet()
		.stream()
		.sorted(Map.Entry.comparingByKey(
								Comparator.reverseOrder()))
		.collect(Collectors.toMap(
							e -> e.getKey(), 
							e -> e.getValue(), 
							(o1, o2) -> o1, 
							LinkedHashMap::new));

5. public static <K, V extends Comparable<? super V>>
Comparator<Map.Entry<K, V>> comparingByValue()

comparingByValue() method is used to compare using natural order of value. It is mandatory that value in Entry implements the Comparable interface. If the value in Entry doesn’t implement Comparable interface then compiler gives compile time error.

Map<String, String> map = new HashMap<>();
map.put("%", "Percent");
map.put("*", "Asterisk");
map.put("~", "Tilde");
map.put("$", "Dollar");
map.put("#", "Octothorpe");

Comparator<Entry<String, String>> comparingByValue 
	= Map.Entry.comparingByValue();

Map<String, String> result 
	= map
		.entrySet()
		.stream()
		.sorted(comparingByValue)
		.collect(Collectors.toMap(
							e -> e.getKey(), 
							e -> e.getValue(), 
							(o1, o2) -> o1, 
							LinkedHashMap::new));

Input
# Octothorpe
$ Dollar
% Percent
* Asterisk
~ Tilde

Output
* Asterisk
$ Dollar
# Octothorpe
% Percent
~ Tilde

6. public static <K, V> Comparator<Map.Entry<K, V>>
comparingByValue(Comparator<? super V> cmp)

In this flavor of comparingByValue() method we can pass in Comparator to provide our own sorting strategy.

Example : Given Map<Long, Transaction> i.e. transactionId : transaction mapping sort it using transaction date.

Map<Long, Transaction> txns = Transactions.getDataSet();

Comparator<Entry<Long, Transaction>> comparingByDate 
	= Entry.comparingByValue(
		(Transaction o1, Transaction o2) 
						-> o1.date().compareTo(o2.date()));

Map<Long, Transaction> result 
	= txns
		.entrySet()
		.stream()
		.sorted(comparingByDate)
		.collect(Collectors.toMap(
							e -> e.getKey(), 
							e -> e.getValue(), 
							(o1, o2) -> o1, 
							LinkedHashMap::new));

Input (Transaction id : date : country)
4868342123897377577 2020-06-29 US
3172651983965964484 2020-07-03 US
3531067141265724515 2020-07-03 CA
4788928807359802107 2020-07-08 AU

Output (Transaction id : date : country)
4868342123897377577 2020-06-29 US
3172651983965964484 2020-07-03 US
3531067141265724515 2020-07-03 CA
4788928807359802107 2020-07-08 AU

Let us chain two comparators. Given Map<Long, Transaction> i.e. transactionId : transaction mapping sort it using transaction date. If the date of transaction is the same sort then by country in ascending order.

Comparator<Entry<Long, Transaction>> comparingByDate 
	= Entry.comparingByValue(
		(Transaction o1, Transaction o2) 
						-> o1.date().compareTo(o2.date()));

Comparator<Entry<Long, Transaction>> comparingByCountry 
	= Entry.comparingByValue(
		(Transaction o1, Transaction o2) 
				-> o1.country().getAlpha2()
					.compareTo(o2.country().getAlpha2()));

Map<Long, Transaction> result 
	= txns
		.entrySet()
		.stream()
		.sorted(comparingByDate.thenComparing(comparingByCountry))
		.collect(Collectors.toMap(
							e -> e.getKey(), 
							e -> e.getValue(), 
							(o1, o2) -> o1, 
							LinkedHashMap::new));

7. Conclusion

In this article we saw how we can sort the Map using key and value by its natural order. Furthermore, we can custom sort the Map by providing our own Comparator. 

Leave a Reply

Your email address will not be published. Required fields are marked *