1. Overview
Map is a common data type when we need to manage key-value associations. The LinkedHashMap is a popular choice, primarily known for preserving the insertion order. However, in many real-world scenarios, we often need to sort the elements of a LinkedHashMap based on their values rather than keys.
In this tutorial, we’ll explore how to sort a LinkedHashMap by values in Java.
2. Sorting by Value
The default behavior of a LinkedHashMap is to maintain the order of elements based on the insertion order. This is useful in cases where we want to keep track of the sequence in which elements were added to the map. However, sorting by values is a different requirement. We might like to arrange the entries in ascending or descending order based on the values associated with the keys.
Next, let’s see an example. Let’s say we have a LinkedHashMap called MY_MAP:
static LinkedHashMap<String, Integer> MY_MAP = new LinkedHashMap<>();
static {
MY_MAP.put("key a", 4);
MY_MAP.put("key b", 1);
MY_MAP.put("key c", 3);
MY_MAP.put("key d", 2);
MY_MAP.put("key e", 5);
}
As the example above shows, we initialized MY_MAP using a static block. The values in the map are integers. Our goal is to sort the map by the values and get a new LinkedHashMap which is equal to EXPECTED_MY_MAP:
static LinkedHashMap<String, Integer> EXPECTED_MY_MAP = new LinkedHashMap<>();
static{
EXPECTED_MY_MAP.put("key b", 1);
EXPECTED_MY_MAP.put("key d", 2);
EXPECTED_MY_MAP.put("key c", 3);
EXPECTED_MY_MAP.put("key a", 4);
EXPECTED_MY_MAP.put("key e", 5);
}
Next, we’ll see several approaches to solving the problem. We’ll use unit test assertions to verify each solution.
3. Using the Collections.sort() Method
First, let’s see how to solve the problem if our Java is older than Java 8.
LinkedHashMap’s entrySet() provides access to all entries while maintaining their original order.
We can also leverage the Collections.sort() method, which allows us to sort a collection of objects by a given Comparator.
Let’s look at the solution first:
List<Map.Entry<String, Integer>> entryList = new ArrayList<>(MY_MAP.entrySet());
Collections.sort(entryList, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
LinkedHashMap<String, Integer> result = new LinkedHashMap<>();
for (Map.Entry<String, Integer> e : entryList) {
result.put(e.getKey(), e.getValue());
}
assertEquals(EXPECTED_MY_MAP, result);
Let’s walk through the code quickly to understand how it works.
First, we wrap entrySet()’s result in a List. Then, we created an anonymous Comparator to sort the entries by their values and pass it to the Collections.sort() method. Finally, we create a new LinkedHashMap object and put the sorted entries into it.
4. Using forEachOrdered()
Stream API is a significant new feature that Java 8 brought us. It allows us to manipulate collections conveniently. Therefore, if the Java version we work with is 8 or later, we can fill an empty LinkedHashMap with sorted entries from the original map using the Stream API:
LinkedHashMap<String, Integer> result = new LinkedHashMap<>();
MY_MAP.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue())
.forEachOrdered(entry -> result.put(entry.getKey(), entry.getValue()));
assertEquals(EXPECTED_MY_MAP, result);
As we can see, using the Stream API, the solution is more fluent and compact.
It’s worth noting that Map.Entry supports the comparingByValue() method. As its name implies, it returns a Comparator that compares entries by their values.
As the Entry.value in our example is Integer, which is Comparable, we can just call comparingByValue() directly.
5. Using collect()
An alternative, more streamlined approach involves leveraging the collect() method to both create the map and accumulate the sorted entries in one shot:
LinkedHashMap<String, Integer> result = MY_MAP.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue())
.collect(LinkedHashMap::new, (map, entry) -> map.put(entry.getKey(), entry.getValue()), Map::putAll);
assertEquals(EXPECTED_MY_MAP, result);
The collect() method is the key to this approach. It takes three parameters:
- Supplier (LinkedHashMap::new) – Provide a new container (LinkedHashMap) to accumulate the results
- Accumulator ((map, entry) -> map.put(entry.getKey(), entry.getValue())) – This function is applied to each element in the stream and adds each entry to the accumulating LinkedHashMap
- Combiner (Map::putAll) – In parallel processing, it combines the containers updated by multiple accumulators. In this case, it’s irrelevant, as the stream is processed sequentially.
Thus, collect() accumulates the sorted entries into a new LinkedHashMap.
6. When the Values Are Not Comparable
We’ve seen how to sort MY_MAP by value. Since the Integer value is Comparable, when we use Stream API, we can simply call sorted(Map.Entry.comparingByValue()).
But, if the value is not Comparable, we need to pass a Comparator to comparingByValue():
class Player {
private String name;
private Integer score = 0;
public Player(String name, Integer score) {
this.name = name;
this.score = score;
}
// ... hashcode, equals, getters methods are omitted ...
}
As the code shows, the Player class doesn’t implement Comparable. Now, let’s initialize a LinkedHashMap<String, Player>:
static LinkedHashMap<String, Player> PLAYERS = new LinkedHashMap<>();
static {
PLAYERS.put("player a", new Player("Eric", 9));
PLAYERS.put("player b", new Player("Kai", 7));
PLAYERS.put("player c", new Player("Amanda", 20));
PLAYERS.put("player d", new Player("Kevin", 4));
}
Let’s say we would like to sort PLAYERS by players’ scores and get a new LinkedHashMap:
static LinkedHashMap<String, Player> EXPECTED_PLAYERS = new LinkedHashMap<>();
static {
EXPECTED_PLAYERS.put("player d", new Player("Kevin", 4));
EXPECTED_PLAYERS.put("player b", new Player("Kai", 7));
EXPECTED_PLAYERS.put("player a", new Player("Eric", 9));
EXPECTED_PLAYERS.put("player c", new Player("Amanda", 20));
}
So next, let’s see how to achieve that:
LinkedHashMap<String, Player> result = PLAYERS.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue(Comparator.comparing(Player::getScore)))
.collect(LinkedHashMap::new, (map, entry) -> map.put(entry.getKey(), entry.getValue()), Map::putAll);
assertEquals(EXPECTED_PLAYERS, result);
In this instance, we utilized Comparator.comparing(Player::getScore) within the comparingByValue() method.
This construct generates a Comparator through an instance method reference, specifically comparing the score field of Player objects.
7. Conclusion
In this tutorial, we’ve explored different ways to sort a LinkedHashMap by values. We also addressed sorting implementation in scenarios where the values do not implement the Comparable interface.
As always, the complete source code for the examples is available over on GitHub.