Transform and filter a Java Map with streams

Solution 1:

Yes, you can map each entry to another temporary entry that will hold the key and the parsed integer value. Then you can filter each entry based on their value.

Map<String, Integer> output =
    input.entrySet()
         .stream()
         .map(e -> new AbstractMap.SimpleEntry<>(e.getKey(), Integer.valueOf(e.getValue())))
         .filter(e -> e.getValue() % 2 == 0)
         .collect(Collectors.toMap(
             Map.Entry::getKey,
             Map.Entry::getValue
         ));

Note that I used Integer.valueOf instead of parseInt since we actually want a boxed int.


If you have the luxury to use the StreamEx library, you can do it quite simply:

Map<String, Integer> output =
    EntryStream.of(input).mapValues(Integer::valueOf).filterValues(v -> v % 2 == 0).toMap();

Solution 2:

One way to solve the problem with much lesser overhead is to move the mapping and filtering down to the collector.

Map<String, Integer> output = input.entrySet().stream().collect(
    HashMap::new,
    (map,e)->{ int i=Integer.parseInt(e.getValue()); if(i%2==0) map.put(e.getKey(), i); },
    Map::putAll);

This does not require the creation of intermediate Map.Entry instances and even better, will postpone the boxing of int values to the point when the values are actually added to the Map, which implies that values rejected by the filter are not boxed at all.

Compared to what Collectors.toMap(…) does, the operation is also simplified by using Map.put rather than Map.merge as we know beforehand that we don’t have to handle key collisions here.

However, as long as you don’t want to utilize parallel execution you may also consider the ordinary loop

HashMap<String,Integer> output=new HashMap<>();
for(Map.Entry<String, String> e: input.entrySet()) {
    int i = Integer.parseInt(e.getValue());
    if(i%2==0) output.put(e.getKey(), i);
}

or the internal iteration variant:

HashMap<String,Integer> output=new HashMap<>();
input.forEach((k,v)->{ int i = Integer.parseInt(v); if(i%2==0) output.put(k, i); });

the latter being quite compact and at least on par with all other variants regarding single threaded performance.

Solution 3:

Guava's your friend:

Map<String, Integer> output = Maps.filterValues(Maps.transformValues(input, Integer::valueOf), i -> i % 2 == 0);

Keep in mind that output is a transformed, filtered view of input. You'll need to make a copy if you want to operate on them independently.