I have the following code that I’d like to try to write using the java collectors.
Given 2 attributes (firstname and lastname) of a person, I’d like to get a map containing the unique firstname or lastname as a key, and the list of the corresponding persons.
Here’s a set of data :
Person person1 = new Person(); person1.setFirstName("john"); person1.setLastName("doe"); person1.setUserId("user1"); Person person2 = new Person(); person2.setFirstName("doe"); person2.setLastName("frank"); person2.setUserId("user2"); Person person3 = new Person(); person3.setFirstName("john"); person3.setLastName("wayne"); person3.setUserId("user3"); List<Person> personList = new ArrayList<>(); personList.add(person1); personList.add(person2); personList.add(person3);
Output (as expected) is the following :
frank=[Person{userId='user2', firstName='doe', lastName='frank'}], john=[Person{userId='user1', firstName='john', lastName='doe'}, Person{userId='user3', firstName='john', lastName='wayne'}], doe=[Person{userId='user1', firstName='john', lastName='doe'}, Person{userId='user2', firstName='doe', lastName='frank'}], wayne=[Person{userId='user3', firstName='john', lastName='wayne'}]
And the code to populate the map :
Map<String, List<Person>> mapPersons = new HashMap<String, List<Person>>(); List<Person> listPersons; for (Person p: personList) { if (mapPersons.get(p.getFirstName()) == null) { listPersons = new ArrayList<Person>(); listPersons.add(p); mapPersons.put(p.getFirstName(), listPersons); } else { mapPersons.get(p.getFirstName()).add(p); } if (mapPersons.get(p.getLastName()) == null) { listPersons = new ArrayList<Person>(); listPersons.add(p); mapPersons.put(p.getLastName(), listPersons); } else { mapPersons.get(p.getLastName()).add(p); } }
I can’t figure out how I can get either the firstname or the lastname as a key (not like in Group by multiple field names in java 8). Do I have to write my own collector?
Advertisement
Answer
You can use Stream.flatMap()
and Collectors.groupingBy()
with Collectors.mapping()
:
Map<String, List<Person>> result = personList.stream() .flatMap(p -> Stream.of(p.getFirstName(), p.getLastName()).map(n -> new AbstractMap.SimpleEntry<>(n, p))) .collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
This uses flatMap
to expand all names (first and last) to their Person
object and collects it afterwards.
Alternatively using Java 9 or above you could use Collectors.flatMapping()
:
Map<String, List<Person>> result = personList.stream() .collect(Collectors.flatMapping( p -> Stream.of(p.getFirstName(), p.getLastName()).map(n -> new AbstractMap.SimpleEntry<>(n, p)), Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList()))));
But I don’t think that this is more readable.