9

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?

5
  • 1
    As a side note (irrelevant to your question), there are easier ways to put a value into the list if absent using the newer methods Map.computeIfAbsent etc. (docs.oracle.com/javase/8/docs/api/java/util/…) Commented Jul 9, 2019 at 12:27
  • True ! But that's an old code that I just took from a java 6 app! Commented Jul 9, 2019 at 12:30
  • 1
    Since every item eventually ends up as two keys, toMap and groupingBy won't work. I assume writing your own collector is the best way to go. Alternatively, duplicating every item with the firstname/lastname as key (as in Samuels answer) works too. Commented Jul 9, 2019 at 12:35
  • 1
    Also should the title be changed to "map one item with multiple fields to multiple keys"? Commented Jul 9, 2019 at 12:37
  • 1
    @sfiss’ comment is valuable as rewriting the loop to use computeIfAbsent may end up at simpler code than the Stream solution. Commented Jul 9, 2019 at 13:55

1 Answer 1

10

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.