【问题标题】:Collect results of a map operation in a Map using Collectors.toMap or groupingBy使用 Collectors.toMap 或 groupingBy 在 Map 中收集地图操作的结果
【发布时间】:2019-10-02 14:44:02
【问题描述】:

我有一个 List<A> 类型的列表,并且通过 map 操作获得了一个 List<B> 类型的集合列表,用于合并到一个列表中的所有 A 元素。

List<A> listofA = [A1, A2, A3, A4, A5, ...]

List<B> listofB = listofA.stream()
  .map(a -> repo.getListofB(a))
  .flatMap(Collection::stream)
  .collect(Collectors.toList());

没有平面图

List<List<B>> listOflistofB = listofA.stream()
  .map(a -> repo.getListofB(a))
  .collect(Collectors.toList());

我想将结果收集为Map&lt;A, List&lt;B&gt;&gt; 类型的地图,到目前为止,我尝试了各种Collectors.toMapCollectors.groupingBy 选项,但无法获得所需的结果。

【问题讨论】:

  • 您是否在您的List&lt;A&gt; 中重复了A 元素?

标签: java lambda java-8 java-stream collectors


【解决方案1】:

您可以使用带有有限方法引用的toMap 收集器来获得所需的内容。另请注意,此解决方案假定您的源容器中没有重复的 A 实例。如果该前提条件成立,则此解决方案将为您提供所需的结果。这是它的外观。

Map<A, Collection<B>> resultMap = listofA.stream()
    .collect(Collectors.toMap(Function.identity(), repo::getListofB);

如果您有重复的 A 元素,那么除了上面给出的内容之外,您还必须使用此合并功能。如果有任何键冲突,merge 函数会处理它们。

Map<A, Collection<B>> resultMap = listofA.stream()
       .collect(Collectors.toMap(Function.identity(), repo::getListofB, 
            (a, b) -> {
                a.addAll(b);
                return a;
        }));

这是一种更简洁的 Java9 方法,它使用 flatMapping 收集器来处理重复的 A 元素。

Map<A, List<B>> aToBmap = listofA.stream()
        .collect(Collectors.groupingBy(Function.identity(),
                Collectors.flatMapping(a -> getListofB(a).stream(), 
                        Collectors.toList())));

【讨论】:

  • 嘿,很高兴您添加了如何处理重复项!我正在添加一个答案,我会这样做,因为我认为它可以补充你的答案,干杯!
  • 是的,您的评论很有帮助。好收获!
【解决方案2】:

这很简单,

listofA.stream().collect(toMap(Function.identity(), a -> getListofB(a)));

【讨论】:

    【解决方案3】:

    在这个答案中,我将展示如果您在 List&lt;A&gt; listofA 列表中重复了 A 元素会发生什么。

    实际上,如果listofA 中有重复项,下面的代码会抛出一个IllegalStateException

    Map<A, Collection<B>> resultMap = listofA.stream()
            .collect(Collectors.toMap(
                                Function.identity(), 
                                repo::getListofB);
    

    可能会抛出异常,因为Collectors.toMap 不知道在键发生冲突时如何合并 值(即,当键映射器函数返回重复项时,因为它将是如果listofA 列表中有重复元素,则为Function.identity() 的大小写)。

    这是明确说明in the docs

    如果映射的键包含重复项(根据Object.equals(Object)),则在执行收集操作时会抛出IllegalStateException。如果映射的键可能有重复,请改用toMap(Function, Function, BinaryOperator)。

    文档也给了我们解决方案:如果有重复的元素,我们需要提供一种合并值的方法。这是一种这样的方式:

    Map<A, Collection<B>> resultMap = listofA.stream()
            .collect(Collectors.toMap(
                                Function.identity(), 
                                a -> new ArrayList<>(repo.getListofB(a)),
                                (left, right) -> {
                                    left.addAll(right);
                                    return left;
                                });
    

    这使用Collectors.toMap 的重载版本,它接受一个合并函数 作为它的第三个参数。在合并函数中,Collection.addAll 被用于将每个重复的 A 元素的 B 元素添加到每个 A 的 unqiue 列表中。

    在值映射器函数中,会创建一个新的ArrayList,这样每个A 的原始List&lt;B&gt; 不会发生变异。另外,当我们创建一个Arraylist 时,我们提前知道它可以被变异(即我们可以稍后向它添加元素,以防listofA 中有重复项)。

    【讨论】:

      【解决方案4】:

      要收集Map,其中键是未更改的A对象,值是对应B对象的列表,您可以将toList()收集器替换为以下收集器:

      toMap(Function.identity(), a -> repo.getListOfB(a))
      

      first 参数定义了如何从原始对象计算 keyidentity() 将流的原始对象保持不变。

      second 参数定义了 value 的计算方式,因此这里它只包含对您的方法的调用,该方法将 A 转换为 @987654328 列表@。

      由于repo 方法只接受一个参数,您还可以通过将 lambda 替换为方法引用来提高清晰度:

      toMap(Function.identity(), repo::getListOfB)
      

      【讨论】:

      • 感谢@kgautron,您的回答是正确的并且解释清楚。我接受了另一个答案,因为我首先看到了它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-20
      • 2017-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-31
      • 2014-12-10
      相关资源
      最近更新 更多