无法应用 Merge 函数转换为在 Stream 输出上获取 Set

Unable to apply Merge function to convert to get Set on a Stream output

这是一个示例源数据结构,用于简化我的场景的问题案例数据。 此地图对不同的代码有重复的描述。

Map<Integer, String> descriptionsByCode = new HashMap();
descriptionsByCode.put(1, "description 1");
descriptionsByCode.put(2, "description 2");
descriptionsByCode.put(4, "description 3");
descriptionsByCode.put(5, "description 4");
descriptionsByCode.put(6, "description 5");
descriptionsByCode.put(7, "description 5");
descriptionsByCode.put(8, "description 5");
descriptionsByCode.put(9, "description 2");
descriptionsByCode.put(10, "description 2");
descriptionsByCode.put(11, "description 3");
descriptionsByCode.put(12, "description 4");
descriptionsByCode.put(13, "description 1");
descriptionsByCode.put(14, "description 6");
descriptionsByCode.put(15, "description 8");

我也有一个复杂的业务对象,但为了简单起见,我们假设有一个只有 2 个字段的对象。

@Data
@AllArgsConstructor
class StatusCounts {
    private String description;
    private Map<Integer, Integer> someIemCountByStatusCode; //defaults to zero for our example
}

如果,我想要一个 StatusCount 对象的地图,按描述。 我可以轻松拥有它。下面是一个工作代码,添加到这里是为了集中解决此后提到的主要问题

     Map<String, StatusCounts> statusCountsByDescription
            = descriptionsByCode
            .entrySet()
            .stream()
            .collect(Collectors.toMap(
                    e -> e.getValue(),  //key function
                    e -> new StatusCounts(e.getValue(),   //value function 
                            Collections.singletonMap(e.getKey(), 0)),
                    (e, v) ->  // merge function for collision 
                    { // is there a better way to write below code ? 
                        Map m = new HashMap();
                        m.putAll(e.getItemCountByStatusCode());
                        m.putAll(v.getItemCountByStatusCode());
                        e.setItemCountByStatusCode(m);
                        return e;
                    }
            ));

以上代码不干净,但运行良好。 我真正想要的是一个 Set 而不是 Map

我无法在 reducecollect 函数中找到一种方法来为 collision detection 提供处理(就像合并函数在 Collectors.toMap 的情况下所做的那样) ).

reduce 或 collect 中的组合器功能对碰撞不起作用。
Collectors.toSet 甚至不接受任何参数。 所以我虽然可以在 reduce 的情况下在 accumulator 中或在 collect 的情况下在或 Biconsumer 中编写碰撞检测处理。所以我在下面尝试使用 collect & BiConsumer 但是这个是不可编译的reduce

Set<StatusCounts> statusCounts
            = descriptionsByCode
            .entrySet()
            .stream()
            .collect(() -> new HashSet<StatusCounts>(),
                    (set, entry) -> {
                        Optional<StatusCounts> statusCount
                                = findStatusCountWithSameDescription(set, entry.getValue());

                        if (statusCount.isPresent()) {
                            statusCount.get()
                                        .getItemCountByStatusCode()
                                        .put(entry.getKey(), 0);
                        } else {
                            set.add(new StatusCounts(
                                    entry.getValue(),
                                    Collections.singletonMap(entry.getKey(), 0)));
                        }
                        return set;
                    }
            );



private static Optional<StatusCounts> findStatusCountWithSameDescription
       (HashSet<StatusCounts> set, String description) {
        return set.stream()
                  .filter(e -> e.getDescription()
                                .equalsIgnoreCase(description))
                  .findFirst();
    }

这是编译错误

Cannot resolve method 'collect(<lambda expression>, <lambda expression>)'

您可以使用 Collectors.groupingBy 来解决这个问题:

Map<String, Set<Integer>> map 
    = descMap.entrySet()
             .stream()
             .collect(Collectors.groupingBy(Entry::getValue, 
                                            Collectors.mapping(Entry::getKey, Collectors.toSet())));                                       

这里会生成一张以描述为key的地图。 Collectors.mapping 将每个条目映射到状态代码,然后将它们组合成一个 Set

我认为 Set<Integer> 而不是 Map<Integer, Integer>StatusCounts class 中就足够了。这是因为从代码来看,您似乎只想知道描述下的不同代码。在这种情况下,您可以尝试以下操作:

return map.entrySet()
          .stream()
          .map(entry -> new StatusCounts(entry.getKey(), entry.getValue()))
          .collect(Collectors.toSet());

更新

如果不允许更改现有的 StatusCounts class,那么您可以尝试以下操作:

Map<String, Map<Integer, Integer>> tempMap
    = descMap.entrySet()
             .stream()
             .collect(Collectors.groupingBy(Entry::getValue, 
                                            Collectors.mapping(Entry::getKey, Collectors.toMap(Function.identity(), v -> 0))));

// You may combine the below code with above instead of using a tempMap.
// I have separated it for simplicity
Set<StatusCounts> result 
    = tempMap.entrySet()
             .stream()
             .map(entry -> new StatusCounts(entry.getKey(), entry.getValue()))
             .collect(Collectors.toSet());