简体   繁体   中英

How do I take a list, collect it to a map, then sort using Java Streams?

I have the following code....

public Map<Object, Integer> getRankings(){
    Stream<String> stream = votes.stream();
    return stream
            .collect(Collectors.toMap(s -> s, s -> 1, Integer::sum));
}

This works great but now I want to sort the map based on count. I tried a sort after the collect but the method isn't available because the stream is now a map. How would I sort this before returning it? Can I do it using a Stream or do I have to sort as a map?

Maps are fundamentally unsorted. You cannot therefore imply an order.

However, some specific subclasses of map do include a definition of ordering. The point is, java.util.Map itself does not, so we must delve into the subclasses; For example, a new java.util.HashMap cannot be ordered , period.

LinkedHashMap is sorted by 'insertion order', and thus, you can enforce an order for it.

SortedMap classes (such as TreeMap) are sorted on key , with a comparator, and therefore it is not possible to sort them on the value either.

The general answer is that you had some problem X and thought: I know. I'll fix this unknown problem X by having a map that is ordered by value, But it is highly unlikely that is a good way to solve X, Unfortunately, you didn't say what X is: you are merely asking questions about how to get a map sorted on values in java, and thus we're now stuck on. Ooooof, that is really hard to do.

Just in case you still think this 'map ordered on values' idea of yours is how to solve X:

There is no .toLinkedHashMap . There IS a variant of Collectors.toMap with a fourth argument which is a lambda that makes the map for you. Use that one, passing in a lambda that makes a new LinkedHashMap . Then you need to coerce the stream API to insert in the right order, which is impossible.

So, we need to make that possible: First collect to a plain jane map. Then re-spin that back out as a stream by asking that map for its entryset and streaming over it. Then, turn that stream into a sorted stream, sorting on entry.getValue() . Then collect the resulting sequential sorted stream back into a map, using one of the Collectors.toMap methods that let you provide the mapSupplier. You don't need the grouping toMap here - your stream objects are ready to be inserted verbatim.

This is of course, inefficient and has an intermediate stage map which is then immediately tossed in the garbage. But it's the only way to do this, which goes back: Having maps-sorted-on-value is rather tricky.

This solution works but I would give the check to a more "streamy" Way.

public Map<Object, Integer> getRankings(){
    Stream<String> stream = votes.stream();
    Map<Object, Integer> map = stream
            .collect(Collectors.toMap(s -> s, s -> 1, Integer::sum));
    return Vote.sortByValues(map);
}

public static <K, V extends Comparable<V>> Map<K, V> sortByValues(final Map<K, V> map) {
    Comparator<K> valueComparator =  new Comparator<K>() {
        public int compare(K k1, K k2) {
            int compare = map.get(k2).compareTo(map.get(k1));
            if (compare == 0) return 1;
            else return compare;
        }
    };
    Map<K, V> sortedByValues = new TreeMap<K, V>(valueComparator);
    sortedByValues.putAll(map);
    return sortedByValues;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM