patternjavaMinor
Lambda to obtain a new merged object
Viewed 0 times
obtainnewmergedobjectlambda
Problem
The requirement is to iterate over a list of
I have achieved this with 2 streams and a private method to do this:
and the
Can I obtain the same result by using only a single lambda operation that is more elegant than the above? I have looked into
Foos, and when 2 Foos have the same id merge their list of inputs together and obtain a new object.I have achieved this with 2 streams and a private method to do this:
import java.util.HashSet;
import java.util.Set;
class Foo {
private String id;
private Set inputs = new HashSet<>();
String getId() {
return id;
}
void setId(String id) {
this.id = id;
}
Set getInputs() {
return inputs;
}
}and the
GetMatchingFoos class:import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import java.util.List;
import java.util.Map;
public class GetMatchingFoos {
public List call(List matchedFoos) {
Map> idToMatchedFooMap = matchedFoos.stream()
.collect(groupingBy(Foo::getId, toList()));
List mergedAndMatchedFoos = idToMatchedFooMap.values().stream()
.map(this::mergeFoos)
.collect(toList());
return mergedAndMatchedFoos;
}
private Foo mergeFoos(List matchedFoos) {
Foo result = new Foo();
for(Foo matchedFoo : matchedFoos) {
result.setId(matchedFoo.getId());
result.getInputs().addAll(matchedFoo.getInputs());
}
return result;
}
}Can I obtain the same result by using only a single lambda operation that is more elegant than the above? I have looked into
reducing but it was not very trivial.Solution
To determine the result you're after, it is necessary to group by the id and hold a temporary map of the grouping operation.
You can make it a bit simpler, and not have a second Stream pipeline, by using the
This code still collects all the foos having the same id into a list, but the use of
This will return a
Note that the current code stores every foo having the same id in a list. This is a bit inefficient since, for the end result, we're interested in a single
The key mapper returns the id of the foo; the value mapper returns a new
This assumes that we have a copy-constructor
You can make it a bit simpler, and not have a second Stream pipeline, by using the
collectingAndThen built-in collector, which gives the ability to pass a finisher operation to the result of another collector.Collection result =
matchedFoos.stream()
.collect(groupingBy(Foo::getId, collectingAndThen(toList(), this::mergeFoos)))
.values();This code still collects all the foos having the same id into a list, but the use of
collectingAndThen enables us to directly pass that list to mergeFoos and return the wanted Foo. Then, it is possible to retrieve the values by invoking values().This will return a
Collection as values() is defined to return that. If you really want a List, you can convert it easily to an ArrayList for example, with:List mergedAndMatchedFoos = new ArrayList<>(result);Note that the current code stores every foo having the same id in a list. This is a bit inefficient since, for the end result, we're interested in a single
Foo where the inputs are all input of the foos in the list. So, instead, we can use the toMap(keyMapper, valueMapper, mergeFunction) collector and directly build the intermediate Map instead a Map>.The key mapper returns the id of the foo; the value mapper returns a new
Foo from a given one, and the merge function adds all inputs from one foo to the other.Map result =
matchedFoos.stream()
.collect(Collectors.toMap(
Foo::getId,
Foo::new,
(foo1, foo2) -> { foo1.getInputs().addAll(foo2.getInputs()); return foo1; }
));
List mergedAndMatchedFoos = new ArrayList<>(result.values());This assumes that we have a copy-constructor
Foo(Foo foo) constructing a Foo from another Foo (which is what the Foo::new method-reference refers to). The mutation of foo1 inside the merge function is safe to do since we're working on the new instance, constructed with the copy constructor.Code Snippets
Collection<Foo> result =
matchedFoos.stream()
.collect(groupingBy(Foo::getId, collectingAndThen(toList(), this::mergeFoos)))
.values();List<Foo> mergedAndMatchedFoos = new ArrayList<>(result);Map<String, Foo> result =
matchedFoos.stream()
.collect(Collectors.toMap(
Foo::getId,
Foo::new,
(foo1, foo2) -> { foo1.getInputs().addAll(foo2.getInputs()); return foo1; }
));
List<Foo> mergedAndMatchedFoos = new ArrayList<>(result.values());Context
StackExchange Code Review Q#140431, answer score: 4
Revisions (0)
No revisions yet.