Reputation: 1015
Is't possible to use filter(),collect() and foreach() in single statement instead of multiple statements?
I have a map and need to filter based on some condition and set some values to the content and return the map. My current looks like below, but i want all 3 in single statement.
Map inputMap (contains all info)
Map<String, Person> returnMap;
returnMap = map.entrySet().stream()
.filter(p -> p.getValue().getCourse() == 123)
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
returnMap.entrySet().stream().forEach((entry) -> {
Person person= entry.getValue();
person.setAction("update");
person.setLastUpdatedTime(new Date());
});
can this be converted to,
Map<String, Person> returnMap;
returnMap = map.entrySet().stream()
.filter(p -> p.getValue().getCourse() == 123)
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()))
.forEach((entry) -> {
Person person= entry.getValue();
person.setAction("update");
person.setLastUpdatedTime(new Date());
});
(this code doesn't work)
Upvotes: 14
Views: 85560
Reputation: 6481
I don't understand the need to separate it into two steps as has been suggested. It seems like the transformation could be done before the terminal operation, like this:
returnMap = map.entrySet().stream()
.filter(p -> p.getValue().getCourse() == 123)
.map(e -> {
Person p = e.getValue();
p.setAction("update");
p.setLastUpdatedTime(new Date());
return e;
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
If you're squeamish about side effects to the Map.Entry
being streamed, you could return a new AbstractMap.SimpleEntry
with the updated Person
as its value, but none of the solutions so far is free of side-effects, since the values in the original Map are being modified. Regardless, here is the variation:
returnMap = map.entrySet().stream()
.filter(p -> p.getValue().getCourse() == 123)
.map(e -> {
Person p = e.getValue();
p.setAction("update");
p.setLastUpdatedTime(new Date());
return new AbstractMap.SimpleEntry<>(e.getKey(), p);
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Upvotes: 2
Reputation: 298459
There is no sense in insisting on doing it with one operation. Regardless of how you write it down, these are two operations.
But one thing you should consider, is, that there are more ways than entrySet().stream()
to process all elements:
Map<String, Person> returnMap = map.entrySet().stream()
.filter(p -> p.getValue().getCourse() == 123)
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
returnMap.values().forEach(person -> {
person.setAction("update");
person.setLastUpdatedTime(new Date());
});
If you still insist on making it look like a single operation, you can do it this way:
Map<String, Person> returnMap = map.entrySet().stream()
.filter(p -> p.getValue().getCourse() == 123)
.collect(Collectors.collectingAndThen(
Collectors.toMap(p -> p.getKey(), p -> p.getValue()),
tmp -> {
tmp.values().forEach(person -> {
person.setAction("update");
person.setLastUpdatedTime(new Date());
});
return tmp;
})
);
This is syntactically a single statement, still, it does exactly the same as the former variant.
Upvotes: 11
Reputation: 3966
The problem is that forEach
does not return an object so you have to handle it different. You can do it this way:
Map<String, Person> returnMap = new HashMap<>();
map.entrySet().stream()
.filter(p -> p.getValue().getCourse() == 123)
.forEach((entry) -> {
Person person = entry.getValue();
person.setAction("update");
person.setLastUpdatedTime(new Date());
returnMap.put(entry.getKey(), person);
});
Upvotes: 25