Home > Software engineering >  Is it possible to sort with grouping by in Spring?
Is it possible to sort with grouping by in Spring?

Time:10-25

List<ManualInfo> manuals = manualRepository.manuals();

manuals.stream()
                .collect(
                        groupingBy( // group 1
                                ManualInfo::getLargeClass,
                                groupingBy( // group 2
                                        ManualInfo::getMediumClass,
                                        groupingBy( //group 3
                                                ManualInfo::getSmallClass,

                                        )
                                )
                        )
                );

In order to respond to the manual in a category structure (json), all manuals are retrieved from the database and then grouped. The categories are structured the way I want them to, but out of order.

I found out after a long time research that ordering before grouping does not work, and that ordering cannot be done after grouping.

As shown below, I recently learned how to group with order, can this be applied to category structure as well?

LinkedHashMap<String, List<ManualInfo>> collect = manuals.stream()
                .collect(groupingBy(ManualInfo::getLargeClass, // group1
                        LinkedHashMap::new, // how can i do group2, group3....?
                        toList()));

CodePudding user response:

If you're fine with the initial order of elements in the list, then you need to use three-args version of groupingBy() which allows to provide a Supplier mapFactory in place of every two-args grouping in your code.

And each of these collectors should specify LinkedHashMap as a desired accumulation type:

List<ManualInfo> manuals = manualRepository.manuals();
        
var resultingMap = manuals.stream()
    .collect(Collectors.groupingBy( // group 1
        ManualInfo::getLargeClass,
        LinkedHashMap::new,
        Collectors.groupingBy( // group 2
            ManualInfo::getMediumClass,
            LinkedHashMap::new,
            Collectors.groupingBy( //group 3
                ManualInfo::getSmallClass,
                LinkedHashMap::new,
                Collectors.toList()
            )
        )
    ));

Note that it's mandatory to provide a downstream collecor as the last argument with this flavor of groupingBy() and for the first and the second collector the downstream would be the subsequent nested grouping, and for the last one we need to provide toList() as a downstream (which is used implicitly under the hood in one-arg version of groupingBy()).

And in case if the ordering of data doesn't match your requirements, then you need to sort the elements before collecting the data.

Assuming that ManualInfo looks like that:

public class ManualInfo {
    private String largeClass;
    private String mediumClass;
    private String smallClass;
    
    // getters, constructors, etc.
}

And let's say you want the data to be sorted first by largeClass, then by mediumClass, and finally by smallClass. Then, using Java 8 static methods of the Comparator interface, we can define the following comparator (you can any sorting criteria you need, it's just an example on how to build the Comparator):

Comparator.comparing(ManualInfo::getLargeClass)
    .thenComparing(ManualInfo::getMediumClass)
    .thenComparing(ManualInfo::getSmallClass)

And the code might look like that:

var resultingMap = manuals.stream()
    .sorted( // imposing the required ordering
        Comparator.comparing(ManualInfo::getLargeClass)
            .thenComparing(ManualInfo::getMediumClass)
            .thenComparing(ManualInfo::getSmallClass)
    )
    .collect(Collectors.groupingBy( // group 1
        ManualInfo::getLargeClass,
        LinkedHashMap::new,
        Collectors.groupingBy( // group 2
            ManualInfo::getMediumClass,
            LinkedHashMap::new,
            Collectors.groupingBy( //group 3
                ManualInfo::getSmallClass,
                LinkedHashMap::new,
                Collectors.toList()
            )
        )
    ));

There's another option of how to impose ordering. You can use TreeMap as accumulation type.

But note that it would only work if the required ordering exactly matches the criteria of grouping, because TreeMap maintains sorted order based on keys.

var resultingMap = manuals.stream()
    .collect(Collectors.groupingBy( // group 1
        ManualInfo::getLargeClass,
        TreeMap::new,
        Collectors.groupingBy( // group 2
            ManualInfo::getMediumClass,
            TreeMap::new,
            Collectors.groupingBy( //group 3
                ManualInfo::getSmallClass,
                TreeMap::new,
                Collectors.toList()
            )
        )
    ));
  • Related