java 被 Java8 Collectors.toMap 弄糊涂了

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33724733/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 22:02:54  来源:igfitidea点击:

Confused by Java8 Collectors.toMap

javajava-8java-streamcollectors

提问by Patrick

I have a collection that looks like below, and I want to filter out the everything except the dates that aren't the end of the months.

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

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

I have the following filter criteria. frequency.getEndreturns a LocalDatematching the end of the month for the given LocalDate.

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

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

So now I think I have to converted this filtered stream back to a map. And I think I use a collector to do that. Thus I add:

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

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

But I don't know what to do with Collectors.toMap. Reading examples leaves me confused. Here's my current code which obviously doesn't work.

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

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

回答by Tunaki

Consider your problem like this: you have a Stream of entry of a map, that is to say a Stream<Map.Entry<LocalDate, BigDecimal>>, and you want to collect it into a TreeMap<LocalDate, BigDecimal>.

像这样考虑您的问题:您有一个地图条目的 Stream,即 a Stream<Map.Entry<LocalDate, BigDecimal>>,并且您想将其收集到 a 中TreeMap<LocalDate, BigDecimal>

So, you are right, you should use Collectors.toMap. Now, as you can see in the documentation, there are actually 3 Collectors.toMap, depending on the arguments:

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

  • toMap(keyMapper, valueMapper). keyMapperis a function whose input is the stream current element and whose output is the key of the final Map. Thus, it maps the Stream element to a key (hence the name). valueMapperis a function whose input is the stream current element and whose output is the value of the final Map.
  • toMap(keyMapper, valueMapper, mergeFunction). The first two parameters are the same as before. The third, mergeFunction, is a function that is called in case of duplicates key elements in the final Map; therefore, its input are 2 values (i.e. the two values for which keyMapperreturned the same key) and merges those two values into a single one.
  • toMap(keyMapper, valueMapper, mergeFunction, mapSupplier). The first three arguments are the same as before. The fourth is a supplier of a Map: as it's currently implemented in the JDK, the two preceding toMapreturn a HashMapinstance. But if you want a specific Map instance, this supplier will return that instance.
  • 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 实例,这个供应商将返回那个实例。

In our specific case, we need to use the third toMap, because we want the result Map to explicitly be a TreeMap. Let's see what input we should give it:

在我们的特定情况下,我们需要使用第三个toMap,因为我们希望结果 Map 显式为 a TreeMap。让我们看看我们应该给它什么输入:

  • keyMapper: so this should return the key of the final Map. Here, we are dealing with a Stream<Map.Entry<LocalDate, BigDecimal>>so each Stream element is of type Map.Entry<LocalDate, BigDecimal>. This function then takes a Map.Entry<LocalDate, BigDecimal> eas input. Its output should be the key of the final Map, in this case, the output should be e.getKey(), i.e. the LocalDatethat the entry is holding. This can be written as a lambda expression: e -> e.getKey(). This could also be written as a method-reference Map.Entry::getKeybut let's stick with lambdas here, because it might be easier to understand.
  • valueMapper: this is the same as above, but, in this case, this function needs to return e.getValue(), i.e. the BigDecimalthat the entry is holding. So this is e -> e.getValue().
  • mergeFunction: this is a tricky one. We know that there are no duplicate key elements (i.e. no duplicate LocalDate) in the final Map, by construction. What do we write here? A simple solution is to throw an exception: this should not happen and if it does, there's a big problem somewhere. So whatever the two input arguments, we'll throw an exception. This can be written as (v1, v2) -> { throw new SomeException(); }. Note that it needs to be enclosed in brackets. In this case, and to be consistent with what the JDK currently does, I've chosen SomeExceptionto be IllegalStateException.
  • mapSupplier: as said before, we want to supply a TreeMap. A supplier takes no argument and returns a new instance. So this can be written as () -> new TreeMap<>()here. Again, we could use a method-reference and write TreeMap::new.
  • keyMapper: 所以这应该返回最终地图的键。在这里,我们正在处理 ,Stream<Map.Entry<LocalDate, BigDecimal>>因此每个 Stream 元素都是类型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: 这是一个棘手的问题。LocalDate通过构造,我们知道最终 Map中没有重复的关键元素(即没有重复)。我们在这里写什么?一个简单的解决方案是抛出一个异常:这不应该发生,如果发生了,那就是某处存在大问题。所以无论两个输入参数如何,我们都会抛出异常。这可以写成(v1, v2) -> { throw new SomeException(); }. 请注意,它需要用括号括起来。在这种情况下,为了与 JDK 当前的功能保持一致,我选择SomeExceptionIllegalStateException.
  • mapSupplier: 如前所述,我们想提供一个TreeMap. 供应商不接受任何参数并返回一个新实例。所以这可以写成() -> new TreeMap<>()这里。同样,我们可以使用方法引用并写入TreeMap::new.

Final code, where I've just written the collecting part of the Stream (note that in this code, you could also use the corresponding method-references, as said above, I added them here in comments):

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

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

回答by Tagir Valeev

In addition to the previous answers note that if you don't need to keep the original map, you can perform such filtering in-place without using the Stream API:

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

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

回答by Andreas

Since you're iterating Map.Entryvalues, and toMap()just needs two methods for extracting the key and the value, it's this simple:

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

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

Note that this will notreturn a TreeMap. For that, you need:

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

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

回答by Tom

The toMap method in its simplest form takes two arguments: one is a function to map the input to the key, and the other a function to map the input to the value. The output of both functions is combined to form an entry in the resulting map.

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

I think you need to do something like this:

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

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