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? */));
Collectors toMap() method in Java with Examples. The toMap() method is a static method of Collectors class which returns a Collector that accumulates elements into a Map whose keys and values are the result of applying the provided mapping functions to the input elements.
For instance, Collectors. toList() method can return an ArrayList or a LinkedList or any other implementation of the List interface. To get the desired Collection, we can use the toCollection() method provided by the Collectors class.
The toList() method of the Collectors class returns a Collector that accumulates the input elements into a new List.
collect() is one of the Java 8's Stream API's terminal methods. It allows us to perform mutable fold operations (repackaging elements to some data structures and applying some additional logic, concatenating them, etc.) on data elements held in a Stream instance.
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
)
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));
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)
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())
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With