In my Java 11 application, I want to get Product Updates from a repository. One Product Update has a updateId
and a list of productIds
to update.
If there are no product numbers that should be updated for update with updateId = X
, I still want to write to another table that I've processed the update X
; updateStatusRepository.setStatusProcessing(updateId)
and updateStatusRepository.setStatusProcessed(updateId)
should still be called for this updateId
.
If there are product updates present, they should be processed in the ProductProcessingService
.
For now, the groupingBy
and mapping
give me a Set with a null
entry instead of an empty set, which is why I later remove all null
Product IDs.
List<ProductUpdate> productUpdateList = updateStatusRepository.getProductUpdates();
Map<String, Set<String>> productUpdateMap = productUpdateList
.stream()
.collect(
Collectors.groupingBy(
ProductUpdate::getUpdateId,
Collectors.mapping(ProductUpdate::getProductNo, Collectors.toSet())));
productUpdateMap.forEach(
(updateId, productIds) -> {
try {
updateStatusRepository.setStatusProcessing(updateId);
productIds.remove(null);
if(!productIds.isEmpty()) {
productProcessingService.performProcessing(Lists.newArrayList(productIds));
}
updateStatusRepository.setStatusProcessed(updateId);
} catch (Exception e) {
//
}
});
I'd prefer if it were possible to use mapping
in such a way that it delivers an empty Set directly if all values are null
.
Is there a way to do this elegantly?
You could use Collectors.filtering
:
Map<String, Set<String>> productUpdateMap = productUpdateList
.stream()
.collect(Collectors.groupingBy(
ProductUpdate::getVersionId,
Collectors.mapping(ProductUpdate::getProductNo,
Collectors.filtering(Objects::nonNull,
Collectors.toSet()))));
I think Collectors.filtering
fits your exact use case: it will filter out null
product numbers, leaving an empty set if all product numbers happen to be null
.
EDIT: Note that in this case, using Collectors.filtering
as a downstream collector is not the same as using Stream.filter
before collecting. In the latter case, if we filtered out elements with a null
product number before collecting, we might end up with a map without entries for some version id, i.e. in case all product numbers are null
for one specific version id.
From Collectors.filtering
docs:
API Note:
The
filtering()
collectors are most useful when used in a multi-level reduction, such as downstream of agroupingBy
orpartitioningBy
. For example, given a stream ofEmployee
, to accumulate the employees in each department that have a salary above a certain threshold:Map<Department, Set<Employee>> wellPaidEmployeesByDepartment = employees.stream().collect( groupingBy(Employee::getDepartment, filtering(e -> e.getSalary() > 2000, toSet())));
A filtering collector differs from a stream's
filter()
operation. In this example, suppose there are no employees whose salary is above the threshold in some department. Using a filtering collector as shown above would result in a mapping from that department to an emptySet
. If a streamfilter()
operation were done instead, there would be no mapping for that department at all.
EDIT 2: I think it's worth mentioning the alternative proposed by @Holger in the comments:
Map<String, Set<String>> productUpdateMap = productUpdateList
.stream()
.collect(Collectors.groupingBy(
ProductUpdate::getVersionId,
Collectors.flatMapping(pu -> Stream.ofNullable(pu.getProductNo()),
Collectors.toSet())));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With