How to group a set of objects into sorted lists using java 8?

I want to take a set of objects ( ObjectInstance in this case), and I want to group them by one property and sort the resulting lists on another.

 Set<ObjectInstance> beans = server.queryMBeans(null, null); Map<String, List<String>> beansByDomain = beans.stream() .collect(groupingBy( (ObjectInstance oi) -> oi.getObjectName().getDomain(), mapping((ObjectInstance oi) -> oi.getObjectName().getCanonicalKeyPropertyListString(), toList() ))); 

The above expression creates the correct data structure: a Map , where the keys are the domains of ObjectInstance objects and the values ​​are lists of property lists. I want to sort the lists now to make sure they are in alphabetical order. Is there a way to do this in one expression?

One idea would be to add .sort() right after .stream() , but does it really work?

+7
java sorting lambda java-stream grouping
source share
2 answers

Use collectingAndThen :

 List<String> beansByDomain = beans.stream() .collect(groupingBy( (ObjectInstance oi) -> oi.getObjectName().getDomain(), mapping((ObjectInstance oi) -> oi.getObjectName().getCanonicalKeyPropertyListString(), collectingAndThen(toList(), (l -> l.stream().sorted().collect(toList()))) ))); 

You can extract the collector to make the code more readable:

 public static <T> Collector<T,?,List<T>> toSortedList() { return Collectors.collectingAndThen(Collectors.toList(), l -> l.stream().sorted().collect(toList())); } List<String> beansByDomain = beans.stream() .collect(groupingBy( (ObjectInstance oi) -> oi.getObjectName().getDomain(), mapping((ObjectInstance oi) -> oi.getObjectName().getCanonicalKeyPropertyListString(), toSortedList()))); 
+6
source share

Of course, you can sort the entire stream before collecting:

 Map<String, List<String>> beansByDomain = beans.stream() .map(ObjectInstance::getObjectName) .sorted(Comparator.comparing(ObjectName::getCanonicalKeyPropertyListString)) .collect(groupingBy(ObjectName::getDomain, mapping(ObjectName::getCanonicalKeyPropertyListString, toList() ))); 

Note that I added the .map(ObjectInstance::getObjectName) step .map(ObjectInstance::getObjectName) , since you don't need anything else from ObjectInstance . This will work well, although I cannot predict whether it will be faster than sorting each resulting list separately or not.

If you prefer a separate toSortingList() collector (as in @JeanLogeart's answer), it can be optimized as follows:

 public static <T extends Comparable<T>> Collector<T,?,List<T>> toSortedList() { return collectingAndThen(toCollection(ArrayList::new), (List<T> l) -> {l.sort(Comparator.naturalOrder()); return l;}); } 

Here we explicitly collect for ArrayList ( toList() does the same, but this is not guaranteed), then sort the resulting list in place without additional copying (using stream().sorted().collect(toList()) , you copy the contents the entire list at least twice). Also note that the <T> parameter must be declared as extends Comparable<T> . Otherwise, you may mistakenly use this collector for a disparate type that will compile fine, but will result in a runtime error.

+2
source share

All Articles