Sorting a map 2 – Functional style programming – extending API
So, when the stream pipeline needs only the map’s values, we can start from values(), when it needs only the keys, we can start from keyset(), and when it needs both (the values and the keys), we can start from entrySet().For instance, a stream pipeline as Map -> Stream -> Filter -> Map that filters the top 5 cars by key and collects them into a resulting map needs the entrySet() starting point as follows:
Map<Integer, Car> carsTop5a = cars.entrySet().stream()
.filter(c -> c.getKey() <= 5)
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue));
//or, .collect(Collectors.toMap(
c -> c.getKey(), c -> c.getValue()));
Here is example that returns a Map of the top 5 cars having more than 100 horsepower:
Map<Integer, Car> hp100Top5a = cars.entrySet().stream()
.filter(c -> c.getValue().getHorsepower() > 100)
.sorted(Entry.comparingByValue(
Comparator.comparingInt(Car::getHorsepower)))
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue,
(c1, c2) -> c2, LinkedHashMap::new));
//or, .collect(Collectors.toMap(
// c -> c.getKey(), c -> c.getValue(),
// (c1, c2) -> c2, LinkedHashMap::new));
If we need to express such pipelines quite often then we may prefer to write some helpers. Here is a set of four generic helpers for filtering and sorting a Map<K, V> by key:
public final class Filters {
private Filters() {
throw new AssertionError(“Cannot be instantiated”);
}
public static <K, V> Map<K, V> byKey(
Map<K, V> map, Predicate<K> predicate) {
return map.entrySet()
.stream()
.filter(item -> predicate.test(item.getKey()))
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue));
}
public static <K, V> Map<K, V> sortedByKey(
Map<K, V> map, Predicate<K> predicate, Comparator<K> c) {
return map.entrySet()
.stream()
.filter(item -> predicate.test(item.getKey()))
.sorted(Map.Entry.comparingByKey(c))
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue,
(c1, c2) -> c2, LinkedHashMap::new));
}
…
And, for filtering and sorting a Map by value:
public static <K, V> Map<K, V> byValue(
Map<K, V> map, Predicate<V> predicate) {
return map.entrySet()
.stream()
.filter(item -> predicate.test(item.getValue()))
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue));
}
public static <K, V> Map<K, V> sortedbyValue(Map<K, V> map,
Predicate<V> predicate, Comparator<V> c) {
return map.entrySet()
.stream()
.filter(item -> predicate.test(item.getValue()))
.sorted(Map.Entry.comparingByValue(c))
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue,
(c1, c2) -> c2, LinkedHashMap::new));
}
}
Now, our code becomes much shorter. For instance, we can filter the top 5 cars by key and collect them into a resulting map as follows:
Map<Integer, Car> carsTop5s
= Filters.byKey(cars, c -> c <= 5);
Or, we can filter the top 5 cars having more than 100 horsepower as follows:
Map<Integer, Car> hp100Top5s
= Filters.byValue(cars, c -> c.getHorsepower() > 100);
Map<Integer, Car> hp100Top5d
= Filters.sortedbyValue(cars, c -> c.getHorsepower() > 100,
Comparator.comparingInt(Car::getHorsepower));
Cool, right?! Feel free to extend Filters with more generic helpers for handling Map processing in stream pipelines.