Reputation: 847
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
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
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
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
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