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

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

在我的 Java 11 应用程序中,我想从存储库获取产品更新。一个产品更新有 updateIdproductIds 列表要更新。

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

如果所有值都是 null.

,我更愿意以这样一种方式使用 mapping,它直接提供一个空集

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

你可以使用 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.[=31,则留下一个空集=]


编辑: 请注意,在这种情况下,使用 Collectors.filtering 作为下游收集器与收集前使用 Stream.filter 不同。在后一种情况下,如果我们在收集之前过滤掉具有 null 产品编号的元素,我们最终可能会得到一个没有某些版本 ID 条目的映射,即如果所有产品编号都是 null具体版本号。

来自 Collectors.filtering 文档:

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.


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

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