Java8 Collectors.toMap困惑

Pat*_*ick 5 java java-8 java-stream collectors

我有一个看起来像下面的集合,我想过滤除了几个月结束之外的所有日期.

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
Run Code Online (Sandbox Code Playgroud)

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

.filter(p -> frequency.getEnd(p.getKey()) == p.getKey())
Run Code Online (Sandbox Code Playgroud)

所以现在我想我必须将这个过滤后的流转换回地图.而且我认为我使用收藏家来做到这一点.因此我补充说:

.collect(Collectors.toMap(/* HUH? */));
Run Code Online (Sandbox Code Playgroud)

但我不知道该怎么做Collectors.toMap.阅读的例子让我很困惑.这是我当前的代码,显然不起作用.

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

Tun*_*aki 21

考虑一下这样的问题:你有一个地图的入口流,也就是说a Stream<Map.Entry<LocalDate, BigDecimal>>,你想把它收集到一个TreeMap<LocalDate, BigDecimal>.

所以,你是对的,你应该使用Collectors.toMap.现在,正如您在文档中看到的那样,实际上有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,因为我们希望结果Map明确地是一个TreeMap.让我们看看我们应该给它什么输入:

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

最后的代码,我刚刚编写了Stream的收集部分(请注意,在此代码中,您也可以使用相应的方法引用,如上所述,我在评论中添加了它们):

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

  • @ njzk2我努力尽可能明确.OP对于一般的收藏家(也许是一般的lambdas)感到困惑所以我认为花时间解释是件好事.我同意最后对6行代码进行了大量的讨论,但我认为这是必要的. (8认同)

Tag*_*eev 5

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

values.keySet().removeIf(k -> !frequency.getEnd(k).equals(k));
Run Code Online (Sandbox Code Playgroud)