Java order TreeMap by value descending using Collections.reverseOrder()

I'd like to sort an existing TreeMap (or copy the values from a Map into a TreeMap, doesn't matter) in an descending order, sorted by value (Doubles).

I know there are a lot of similar questions posted here, but afaik in Java8 you can accomplish this without creating an own Comparator, but using Collections.reverseOrder.

Somewhere there was an answer which described exactly this. Based on it, I tried to implement it:

private Map<String, Double> orderByDescValue(Map<String, Double> unorderedMap) {
    Stream<Map.Entry<String,Double>> sorted = unorderedMap.entrySet().stream()
            .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));
    return sorted.limit(Configuration.WORDCLOUD_SIZE)
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

}

I understand this doesn't work because it returns a Map, which doesn't assert any order, instead of a TreeMap. But Collectors doesn't seem to have a toTreeMap, I can't cast it - and I do not know what else to do.

Or maybe it can't work this way and I have to solve this another way?

Jon Skeet
people
quotationmark

You can use the fact that a LinkedHashMap will retain insertion order - so specify a supplier in the toMap call so that it will create a LinkedHashMap appropriately:

.collect(Collectors.toMap(
    Map.Entry::getKey,
    Map.Entry::getValue,
    (x, y) -> { throw new IllegalStateException("Unexpected merge request"); },
    LinkedHashMap::new));

That won't be a TreeMap, but your method doesn't declare that it returns a TreeMap, just a Map. If you really, really need a TreeMap, I suggest you change the signature - but that would be odd, given that a TreeMap sorts by key rather than value.

Complete example:

import java.util.*;
import java.util.stream.*;

public class Test {
    public static void main(String[] args) throws Exception {
        Map<String, Double> unordered = new HashMap<>();
        unordered.put("a", 10.5);
        unordered.put("b", 5.3);
        unordered.put("c", 12.7);
        unordered.put("d", 6.0);

        Map<String, Double> ordered = orderByDescValue(unordered);
        for (Map.Entry<String, Double> entry : ordered.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

    private static Map<String, Double> orderByDescValue(Map<String, Double> unorderedMap) {
        return unorderedMap.entrySet().stream()
            .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (x, y) -> { throw new IllegalStateException("Unexpected merge request"); },
                LinkedHashMap::new));
    }
}

Output:

c: 12.7
a: 10.5
d: 6.0
b: 5.3

Additionally, you don't need to limit your method to only handle that type of map - you can make it generic:

private static <K, V extends Comparable<V>> Map<K, V> orderByDescValue(Map<K, V> unorderedMap) {
    return unorderedMap.entrySet().stream()
        .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
        .collect(Collectors.toMap(
            Map.Entry::getKey,
            Map.Entry::getValue,
            (x, y) -> { throw new IllegalStateException("Unexpected merge request"); },
            LinkedHashMap::new));
}

(I'm sure I could add a bunch of ? extends K or whatever, but I've left it in a simpler form for now...)

people

See more on this question at Stackoverflow