6

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

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? */));
4

4 回答 4

25

像这样考虑您的问题:您有一个地图条目流,也就是说 a Stream<Map.Entry<LocalDate, BigDecimal>>,并且您想将它收集到 aTreeMap<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 明确地是 a TreeMap。让我们看看我们应该给它什么输入:

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

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

values.keySet().removeIf(k -> !frequency.getEnd(k).equals(k));
于 2015-11-16T03:20:30.350 回答
4

由于您正在迭代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)
于 2015-11-15T20:53:53.853 回答
1

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

我认为你需要做这样的事情:

Collectors.toMap(p -> p.getKey(), p -> p.getValue())
于 2015-11-15T20:55:24.150 回答