Patrick
Patrick

Reputation: 847

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

Upvotes: 7

Views: 20116

Answers (4)

Tagir Valeev
Tagir Valeev

Reputation: 100309

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:

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

Upvotes: 6

Tunaki
Tunaki

Reputation: 137269

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
)

Upvotes: 25

Andreas
Andreas

Reputation: 159185

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

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

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

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

Upvotes: 4

Tom
Tom

Reputation: 1454

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.

I think you need to do something like this:

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

Upvotes: 1

Related Questions