【问题标题】:partitioningBy with a downstream collector produced an unexpected resultpartitioningBy 与下游收集器产生了意外的结果
【发布时间】:2020-12-09 08:41:25
【问题描述】:

我有一个测试程序:

public class App {

    public static void main(String[] args) {

        List<Integer> a = Arrays.asList(1, 11);
        List<Integer> b = Arrays.asList(2, 22);
        List<Integer> c = Arrays.asList(3, 33);

        Map<String, List<Integer>> map = new HashMap<>();
        map.put("a", a);
        map.put("b", b);
        map.put("c", c);

        Set<String> valid = new HashSet<>();
        valid.add("a");

        Map<Boolean, List<Map.Entry<String, List<Integer>>>> partitions =
            map.entrySet().stream()
                .collect(Collectors.partitioningBy(
                    entry -> valid.contains(entry.getKey())));

        System.out.println(partitions);

        // partition by the key of the map
        // then reduce the values into a single collection

        Map<Boolean, List<Integer>> result = map.entrySet().stream()
            .collect(Collectors.partitioningBy(
                entry -> valid.contains(entry.getKey()),
                Collectors.mapping(Map.Entry::getValue,
                                   Collectors.reducing(new ArrayList<>(),
                                                       (l1, l2) -> {
                                                           l1.addAll(l2);
                                                           return l1;
                                                       }))));

        System.out.println(result);
    }
}

我期待最终结果是

{false=[b=[2, 22], c=[3, 33]], true=[a=[1, 11]]}
{false=[2, 22, 3, 33], true=[1, 11]}

但在实际结果中,真假键都是6个整数:

{false=[b=[2, 22], c=[3, 33]], true=[a=[1, 11]]}
{false=[1, 11, 2, 22, 3, 33], true=[1, 11, 2, 22, 3, 33]}

请注意,这 2 个分区函数完全相同。但是下游混合了单独分区中的值。这个怎么可能?我假设下游只会在每个分区上运行......

我错过了什么?

谢谢。

【问题讨论】:

  • 第一个的等价物是Map&lt;Boolean, List&lt;Integer&gt;&gt; result = map.entrySet().stream() .collect(Collectors.partitioningBy( entry -&gt; valid.contains(entry.getKey()), Collectors.flatMapping(e -&gt; e.getValue().stream(), Collectors.toList())));。两个分区谓词相同,下游不一样。
  • @Naman,感谢您的评论。您的代码可能有效,但我使用的是没有 flagMapping 的 Java 8。抱歉,我忘了标记 javaj-8。
  • 为了减少使用相同的 ArrayList 引用,一个简单的解决方法是 List&lt;Integer&gt; l3 = new ArrayList&lt;&gt;();l3.addAll(l2);l3.addAll(l1);return l3;

标签: java java-8 functional-programming java-stream collectors


【解决方案1】:

为了减少在两个分区中使用相同的 ArrayList 引用。 您可以使用Collectors.toMap 并创建一个合并两个列表的新实例。

Map<Boolean, List<Integer>> result = 
        map.entrySet()
        .stream()
        .collect(Collectors.toMap(e -> valid.contains(e.getKey()), Map.Entry::getValue, 
        (l1, l2) -> {
          List<Integer> l3 = new ArrayList<>(l1);
          l3.addAll(l2);
          return l3;
        }));

如果你想用同样的口味

Map<Boolean, List<Integer>> result = 
        map.entrySet()
        .stream()
        .collect(Collectors.toMap(e-> valid.contains(e.getKey()), Map.Entry::getValue,
            (l1, l2) -> Stream.concat(l1.stream(), l2.stream())
                              .collect(Collectors.toList())));

【讨论】:

  • 您的回答有效,所以我会接受。但我更喜欢你上面评论中的建议。问题的症结在于跨分区共享相同的身份对象。
  • @James 实际上,您正在修改 reducer 标识值,这是不应该的,这就是两个分区具有相同 List 实例的原因。按照建议创建新实例而不是修改。
  • 非常正确。不变性是函数式编程的核心。谢谢。
  • 这就是为什么你不应该使用 Reduction 来完成这样的任务,而是 Mutable Reduction 又名收集器,例如Collectors.partitioningBy(entry -&gt; valid.contains(entry.getKey()), Collectors.flatMapping(entry -&gt; entry.getValue().stream(), Collectors.toList()))
【解决方案2】:

要完成already accepted answer,您可以使用Collectors.groupingBy,将Java 9 中的Collectors.flatMapping 用作下游收集器。

Map<Boolean, List<Integer>> result = map.entrySet().stream()
    .collect(Collectors.groupingBy(
         e -> valid.contains(e.getKey()), 
         Collectors.flatMapping(e -> e.getValue().stream(), Collectors.toList())));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多