首页 > 解决方案 > 在深层属性上收集 groupBy

问题描述

private Map<String, Set<Square>> populateZuloSquare(List<Square> squares) {
    if (squares == null || squares.isEmpty()) {
        return emptyMap();
    }

    Map<String, Set<Square>> res = new HashMap<>();

    squares.stream()
        .filter(square -> {
            if (square.getZuloCodes().isEmpty()) {
                LOG("Ignored {}", square.id);
                return false;
            }
            return true;
        })
        .forEach(square -> {
          square.getZuloCodes()
            .forEach(code -> {
                res.putIfAbsent(code, new HashSet<>());
                res.get(code).add(square);
            }));
        });

    return Collections.unmodifiableMap(res);
}

上面的代码接收一个 Squares 列表,这些 Squares 里面可能包含 ZuloCodes。输出应该是一个不可变的 Map zuloCode,并使用该 UniquePrefix 对所有正方形进行赋值。如您所见,我无法找到删除辅助集合 res 并使代码易于阅读的方法,有没有办法将该集合分解为 [zuloCode, square] 然后 collect.groupBy ?此外,如果过滤器内部如此难以阅读,您将如何解决它?

标签: java-8java-streamcollectors

解决方案


标准方法是flatMap在收集 using 之前使用 using groupingBy,但由于您需要每个元素的原始Square内容,因此您需要映射到同时包含Square实例和 zulo 代码的对象String

由于 Java 中还没有标准对或元组类型,因此解决方法是使用实Map.Entry​​例,如下所示

private Map<String, Set<Square>> populateZuloSquare0(List<Square> squares) {
    if (squares == null || squares.isEmpty()) {
        return emptyMap();
    }
    return squares.stream()
        .filter(square -> logMismatch(square, !square.getZuloCodes().isEmpty()))
        .flatMap(square -> square.getZuloCodes().stream()
            .map(code -> new AbstractMap.SimpleEntry<>(code, square)))
        .collect(Collectors.collectingAndThen(
            Collectors.groupingBy(Map.Entry::getKey,
                Collectors.mapping(Map.Entry::getValue, Collectors.toSet())),
            Collections::unmodifiableMap));
}
private static boolean logMismatch(Square square, boolean match) {
    if(!match) LOG("Ignored {}", square.id);
    return match;
}

另一种方法是使用自定义收集器,它将遍历键:

private Map<String, Set<Square>> populateZuloSquare(List<Square> squares) {
    if (squares == null || squares.isEmpty()) {
        return emptyMap();
    }
    return squares.stream()
        .filter(square -> logMismatch(square, !square.getZuloCodes().isEmpty()))
        .collect(Collector.of(
            HashMap<String, Set<Square>>::new,
            (m,square) -> square.getZuloCodes()
                .forEach(code -> m.computeIfAbsent(code, x -> new HashSet<>()).add(square)),
            (m1,m2) -> {
                if(m1.isEmpty()) return m2;
                m2.forEach((key,set) ->
                    m1.merge(key, set, (s1,s2) -> { s1.addAll(s2); return s1; }));
                return m1;
            },
            Collections::unmodifiableMap)
        );
}

请注意,此自定义收集器可以看作是以下循环代码的并行变体:

private Map<String, Set<Square>> populateZuloSquare(List<Square> squares) {
    if (squares == null || squares.isEmpty()) {
        return emptyMap();
    }
    Map<String, Set<Square>> res = new HashMap<>();
    squares.forEach(square -> {
        if(square.getZuloCodes().isEmpty()) LOG("Ignored {}", square.id);
        else square.getZuloCodes().forEach(
            code -> res.computeIfAbsent(code, x -> new HashSet<>()).add(square));
    });
    return Collections.unmodifiableMap(res);
}

当您不需要代码具有并行能力时,现在看起来可能还不错……</p>


推荐阅读