Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
317 views
in Technique[技术] by (71.8m points)

java - Confused by Java8 Collectors.toMap

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.getEnd returns a LocalDate matching the end of the month for the given 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.

TreeMap<LocalDate, BigDecimal> values = values.entrySet()
                                              .stream()
                                              .filter(p -> frequency.getEnd(p.getKey()) == p.getKey())
                                              .collect(Collectors.toMap(/* HUH? */));
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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>.

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:

  • toMap(keyMapper, valueMapper). keyMapper is 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). valueMapper is 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 keyMapper returned 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 toMap return a HashMap instance. But if you want a specific Map instance, this supplier will return that instance.

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:

  • 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> e as input. Its output should be the key of the final Map, in this case, the output should be e.getKey(), i.e. the LocalDate that 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::getKey but 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 BigDecimal that 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 SomeException to 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.

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):

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...