Java通过对set进行分组和映射来收集,但是如果所有值都为null,则需要一个空集

时间:2019-01-10 12:28:42

标签: java java-stream collectors

在我的Java 11应用程序中,我想从存储库中获取产品更新。一个产品更新包含一个updateId和一个productIds列表以进行更新。

  • 如果没有要使用updateId = X更新的产品编号,我仍然想写到另一个已经处理过更新X的表中; updateStatusRepository.setStatusProcessing(updateId)仍应调用updateStatusRepository.setStatusProcessed(updateId)updateId

  • 如果存在产品更新,则应在ProductProcessingService中进行处理。

目前,groupingBymapping给了我一个带有null条目的集合,而不是一个空集合,这就是为什么我后来删除所有null产品ID的原因。

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) {
              //
        }
});

如果可以使用mapping,如果所有值均为null,它可以直接传递一个空Set,则我更愿意。

有没有办法优雅地做到这一点?

1 个答案:

答案 0 :(得分:10)

您可以使用Collectors.filtering

Map<String, Set<String>> productUpdateMap = productUpdateList
      .stream()
      .collect(Collectors.groupingBy(
               ProductUpdate::getVersionId,
               Collectors.mapping(ProductUpdate::getProductNo, 
                                  Collectors.filtering(Objects::nonNull, 
                                                       Collectors.toSet()))));

我认为Collectors.filtering符合您的确切用例:它将过滤掉null个产品编号,如果所有产品编号都恰好是null,则会保留一个空白集。


编辑:请注意,在这种情况下,使用Collectors.filtering作为下游收集器与使用Stream.filter进行收集之前不同。在后一种情况下,如果我们在收集之前过滤出具有null产品编号的元素,则最终可能会得到一个地图,其中没有任何版本ID的条目,即,如果一个产品的所有产品编号均为null特定版本ID。

来自Collectors.filtering文档:

  

API注意:

     

filtering()收集器在多级归约中(例如groupingBypartitioningBy的下游)中最有用。例如,给定Employee流,以累积每个部门中薪水超过特定阈值的员工:

Map<Department, Set<Employee>> wellPaidEmployeesByDepartment
  = employees.stream().collect(
    groupingBy(Employee::getDepartment,
               filtering(e -> e.getSalary() > 2000,
                         toSet())));
     

过滤收集器不同于流的filter()操作。在此示例中,假设在某个部门中没有薪水高于阈值的员工。如上所示,使用过滤器收集器将导致从该部门到空Set的映射。如果改为执行流filter()操作,则该部门将根本没有映射。


编辑2:我认为值得一提的是@Holger在评论中提出的替代方案:

Map<String, Set<String>> productUpdateMap = productUpdateList
      .stream()
      .collect(Collectors.groupingBy(
               ProductUpdate::getVersionId, 
               Collectors.flatMapping(pu -> Stream.ofNullable(pu.getProductNo()), 
                                      Collectors.toSet())));