Say I have two collections of A { id, foo }
and B { identifier, bar }
.
Initially I solved this problem using nested for
-loops, but I did try to solve it (unsuccessful I would say) using the Stream API. Basically I need to traverse both collections looking for matches based on a given condition: A.id === B.identifier
, in order to create a final map of A
elements as keys and the values would be the corresponding A.foo + B.bar
for the matches.
The problem for me in the solution using the Stream API was the second part of the requirements: if some element(s) from A
did not satisfy the condition, a default value should be assigned (based on a function call)... and I don't know how to plug this while using the Stream API along with the rest of the conditions.
Notice that the final collection will have as many elements as (initial)
A
— with the modifications.
Is there an efficient way to accomplish this using the Stream API rather than using nested for
-loops and a couple of if
conditions?
Can you use the temporary collection? If yes, I would suggest to convert List<B>
into Map<Identifier, Bar> mapOfB
, so you can much easier and faster find the specified B.identifier
without need to traverse the B list for each A element.
Then you can simply do something like:
listOfA.stream()
.collect(toMap(a -> a, a -> {
var bar = mapOfB.get(a.getId());
if (bar == null) {
bar = getDefault();
}
return a.getFoo() + bar;
}));
If you can't create the temporary Map:
listOfA.stream()
.collect(toMap(a -> a, a -> {
var bar = getBarFromBOrDefault(listOfB, a.getId());
return a.getFoo() + bar;
}))
private Bar getBarFromBOrDefault(List<B> listOfB, Id id) {
return listOfB.stream()
.filter(b -> b.getIdentity() == id) // or use .equals() here if it's not a primitive type
.findFirst()
.map(B::getBar)
.orElseGet(() -> getDefault());
}