Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java collect with grouping and mapping for `set`, but need an empty set if all values are `null`

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?

like image 556
s-heins Avatar asked Jan 10 '19 12:01

s-heins


1 Answers

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 a groupingBy or partitioningBy. For example, given a stream of Employee, 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 empty Set. If a stream filter() 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())));
like image 115
fps Avatar answered Sep 19 '22 23:09

fps