【问题标题】:Confused by Java8 Collectors.toMap被 Java8 Collectors.toMap 弄糊涂了
【发布时间】:2016-02-16 22:09:06
【问题描述】:

我有一个如下所示的集合,我想过滤掉除非月末日期之外的所有内容。

2010-01-01=2100.00, 
2010-01-31=2108.74, 
2010-02-01=2208.74, 
2010-02-28=2217.92, 
2010-03-01=2317.92, 
2010-03-31=2327.57, 
2010-04-01=2427.57, 
2010-04-30=2437.67, 
2010-05-01=2537.67, 
2010-05-31=2548.22, 
2010-06-01=2648.22, 
2010-06-30=2659.24, 
2010-07-01=2759.24, 
2010-07-31=2770.72, 
2010-08-01=2870.72, 
2010-08-31=2882.66, 
2010-09-01=2982.66, 
2010-09-30=2995.07, 
2010-10-01=3095.07, 
2010-10-31=3107.94, 
2010-11-01=3207.94, 
2010-11-30=3221.29

我有以下过滤条件。 frequency.getEnd 返回与给定 LocalDate 的月底匹配的 LocalDate

.filter(p -> frequency.getEnd(p.getKey()) == p.getKey())

所以现在我想我必须将这个过滤后的流转换回地图。我想我使用收集器来做到这一点。因此我补充说:

.collect(Collectors.toMap(/* HUH? */));

但我不知道如何处理Collectors.toMap。阅读示例让我感到困惑。这是我当前的代码,显然不起作用。

TreeMap<LocalDate, BigDecimal> values = values.entrySet()
                                              .stream()
                                              .filter(p -> frequency.getEnd(p.getKey()) == p.getKey())
                                              .collect(Collectors.toMap(/* HUH? */));

【问题讨论】:

    标签: java java-8 java-stream collectors


    【解决方案1】:

    由于您正在迭代Map.Entry 值,而toMap() 只需要两种方法来提取键和值,就这么简单:

    Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)
    

    请注意,这不会返回TreeMap。为此,您需要:

    Collectors.toMap(Entry::getKey,
                     Entry::getValue,
                     (v1,v2) -> { throw new IllegalStateException("Duplicate key"); },
                     TreeMap::new)
    

    【讨论】:

      【解决方案2】:

      最简单形式的 toMap 方法有两个参数:一个是将输入映射到键的函数,另一个是将输入映射到值的函数。两个函数的输出组合在一起,在结果映射中形成一个条目。

      我认为你需要这样做:

      Collectors.toMap(p -> p.getKey(), p -> p.getValue())
      

      【讨论】:

      • OP 想要一个TreeMap 作为输出,而不是当前默认的HashMap
      • 我看不出他特别想要一个 TreeMap。他展示了一个使用 TreeMap 的示例,但这并不要求它。
      • 提高了评论,因为它解决了我的目的;我意识到这不是 OP 的。
      【解决方案3】:

      像这样考虑你的问题:你有一个地图的条目流,也就是说一个Stream&lt;Map.Entry&lt;LocalDate, BigDecimal&gt;&gt;,你想把它收集到一个TreeMap&lt;LocalDate, BigDecimal&gt;中。

      所以,你是对的,你应该使用Collectors.toMap。现在,正如您在the documentation 中看到的那样,实际上有 3 个Collectors.toMap,具体取决于参数:

      • toMap(keyMapper, valueMapper)keyMapper 是一个函数,其输入为流当前元素,输出为最终 Map 的键。因此,它将 Stream 元素映射到一个键(因此得名)。 valueMapper 是一个函数,其输入为流当前元素,输出为最终 Map 的值。
      • toMap(keyMapper, valueMapper, mergeFunction)。前两个参数和之前一样。第三个,mergeFunction,是一个函数,在最终 Map 中重复关键元素的情况下调用;因此,它的输入是 2 个值(即 keyMapper 返回相同键的两个值)并将这两个值合并为一个值。
      • toMap(keyMapper, valueMapper, mergeFunction, mapSupplier)。前三个参数与之前相同。第四个是 Map 的提供者:因为它目前在 JDK 中实现,前面的两个 toMap 返回一个 HashMap 实例。但是,如果您需要特定的 Map 实例,此供应商将返回该实例。

      在我们的具体情况下,我们需要使用第三个toMap,因为我们希望结果映射明确地是TreeMap。让我们看看我们应该给它什么输入:

      • keyMapper:所以这应该返回最终 Map 的键。在这里,我们处理的是Stream&lt;Map.Entry&lt;LocalDate, BigDecimal&gt;&gt;,因此每个Stream 元素的类型都是Map.Entry&lt;LocalDate, BigDecimal&gt;。然后,此函数将Map.Entry&lt;LocalDate, BigDecimal&gt; e 作为输入。它的输出应该是最终 Map 的 key,在这种情况下,输出应该是 e.getKey(),即条目持有的 LocalDate。这可以写成 lambda 表达式:e -&gt; e.getKey()。这也可以写成方法参考Map.Entry::getKey,但在这里我们还是坚持使用 lambda,因为它可能更容易理解。
      • valueMapper:和上面一样,但是,在这种情况下,这个函数需要返回e.getValue(),即该条目所持有的BigDecimal。所以这是e -&gt; e.getValue()
      • mergeFunction:这是一个棘手的问题。通过构造,我们知道最终 Map 中没有重复的关键元素(即没有重复的 LocalDate)。我们在这里写什么?一个简单的解决方案是抛出异常:这不应该发生,如果发生了,那么某处就有大问题。所以无论两个输入参数,我们都会抛出一个异常。这可以写成(v1, v2) -&gt; { throw new SomeException(); }。请注意,它需要用括号括起来。在这种情况下,为了与 JDK 当前所做的保持一致,我选择 SomeExceptionIllegalStateException
      • mapSupplier:如前所述,我们想提供一个TreeMap。供应商不接受任何参数并返回一个新实例。所以这里可以写成() -&gt; new TreeMap&lt;&gt;()。同样,我们可以使用方法引用并编写TreeMap::new

      最后的代码,我刚刚写了Stream的采集部分(注意,在这段代码中,你也可以使用对应的方法引用,如上所述,我在cmets中添加了它们):

      Collectors.toMap(
             e -> e.getKey(),    // Map.Entry::getKey
             e -> e.getValue(),  // Map.Entry::getValue
             (v1, v2) -> { throw new IllegalStateException(); },
             () -> new TreeMap<>())  // TreeMap::new
      )
      

      【讨论】:

      • 我会,但是太长了
      • @njzk2 好吧,我试图尽可能明确。 OP 对一般的收集器(可能还有一般的 lambdas)感到困惑,所以我认为花时间解释是一件好事。我同意最后6行代码说了一大堆,但我觉得很有必要。
      • 感谢您提供如此详细的信息。我想我会迷失在mergeFunction(因为我没有合并任何东西)和mapSupplier。最后,我在帖子底部添加了一些小模组。
      • @Patrick 不要在帖子末尾添加最终代码,这会破坏 SO 的问答格式。通过接受这个答案,您已经对未来的读者说您认为这是最好的答案。我删除了它。
      • @Tunaki,是的,我是流 api 和收集器的新手,所以我没有将其全部内化。您的详细描述正是我所需要的,因为我的目标是学习而不是要求代码并继续前进。再次感谢。
      【解决方案4】:

      除了前面的答案注意,如果您不需要保留原始地图,您可以在不使用 Stream API 的情况下就地执行此类过滤:

      values.keySet().removeIf(k -> !frequency.getEnd(k).equals(k));
      

      【讨论】:

        猜你喜欢
        • 2022-01-23
        • 2019-01-12
        • 1970-01-01
        • 2014-08-14
        • 1970-01-01
        • 1970-01-01
        • 2017-07-24
        • 2011-08-02
        • 1970-01-01
        相关资源
        最近更新 更多