Issue
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()));
Solution
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()
)
)
));
Answered By - Alexander Ivanchenko
Answer Checked By - Robin (JavaFixing Admin)