I have a collection of Person
objects:.
public class Person {
String name;
ChildrenListHolder childrenListHolder;
}
public class ChildrenListHolder {
List<Children> children;
}
public class Children {
String childrensName;
}
(The entity structure is given by third party.)
Now, I need a Map<String,List<Person>>
childrensName -> person-list
For example (simplified):
Person father: {name: "John", childrensListHolder -> {"Lisa", "Jimmy"}}
Person mother: {name: "Clara", childrensListHolder -> {"Lisa", "Paul"}}
Person george: {name: "George", childrensListHold -> "Paul"}}
The map, I need is
Map<String, List<Person>> map: {"Lisa" -> {father, mother},
"Jimmy" -> {father},
"Paul" -> {mother, george}}
I can do that with a bunch of for's and if's. But how can I do this using streams and collectors. I have tried many approaches, but I cannot get the expected result. TIA.
3.1. The toList collector can be used for collecting all Stream elements into a List instance.
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.
Collectors toList() method in Java with ExamplesIt returns a Collector Interface that gathers the input data onto a new list. This method never guarantees type, mutability, serializability, or thread-safety of the returned list but for more control toCollection(Supplier) method can be used.
Given a List<Person> persons
, you can have the following
Map<String,List<Person>> map =
persons.stream()
.flatMap(p -> p.childrenListHolder.children.stream().map(c -> new AbstractMap.SimpleEntry<>(c, p)))
.collect(Collectors.groupingBy(
e -> e.getKey().childrensName,
Collectors.mapping(Map.Entry::getValue, Collectors.toList())
));
This is creating a Stream over the persons. Then each person is flat mapped by a tuple holding the child and the person for each child. Finally, we group by the child name and collect all the persons into a list.
Sample code assuming there are appropriate constructors:
public static void main(String[] args) {
List<Person> persons = Arrays.asList(
new Person("John", new ChildrenListHolder(Arrays.asList(new Children("Lisa"), new Children("Jimmy")))),
new Person("Clara", new ChildrenListHolder(Arrays.asList(new Children("Lisa"), new Children("Paul")))),
new Person("George", new ChildrenListHolder(Arrays.asList(new Children("Paul"))))
);
Map<String,List<Person>> map =
persons.stream()
.flatMap(p -> p.childrenListHolder.children.stream().map(c -> new AbstractMap.SimpleEntry<>(c, p)))
.collect(Collectors.groupingBy(
e -> e.getKey().childrensName,
Collectors.mapping(Map.Entry::getValue, Collectors.toList())
));
System.out.println(map);
}
I can do that with a bunch of for's and if's.
I know that you asked for a stream/collectors solution, but in any case a nested for loop using Map#computeIfAbsent
works fine too:
Map<String, List<Person>> map = new HashMap<>();
for(Person p : persons) {
for(Children c : p.childrenListHolder.children) {
map.computeIfAbsent(c.childrensName, k -> new ArrayList<>()).add(p);
}
}
and this written using the new forEach
method introduced on collections:
Map<String, List<Person>> map = new HashMap<>();
persons.forEach(p -> p.childrenListHolder.children.forEach(c -> map.computeIfAbsent(c.childrensName, k -> new ArrayList<>()).add(p)));
Of course it's not a one-liner nor easy parallelizable like in Tunaki's solution (+1), but you don't need a "bunch" of if's to achieve that too (and you avoid also the creation of temporary map entries instances).
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