64

I'm trying to do grouping by (to map) and then transform list of values to different list.

I have List of DistrictDocuments:

List<DistrictDocument> docs = new ArrayList<>();

Then I'm streaming over it and grouping it by city:

Map<String, List<DistrictDocument>> collect = docs.stream()
                .collect(Collectors.groupingBy(DistrictDocument::getCity));

I also have a method which takes DistrictDocument and creates Slugable out of it:

private Fizz createFizz(DistrictDocument doc) {
    return new Fizz().name(doc.getName()).fizz(doc.getFizz());
}

Is there a way to put that method into my stream above so I get Map<String, List<Fizz>> ? I tried adding 2nd argument to groupingBy but couldn't find a proper way and been always getting compilation errors.

Edit: What if my createFizz returns List<Fizz> ? Is there an option to flat this list in Collectors.mapping becasue I still want to have Map<String, List<Fizz>> instead of Map<String, List<List<Fizz>>>

Eran
  • 387,369
  • 54
  • 702
  • 768
doublemc
  • 3,021
  • 5
  • 34
  • 61

2 Answers2

116

You need to chain a Collectors.mapping() collector to the Collectors.groupingBy() collector:

Map<String, List<Fizz>> collect =
    docs.stream()
        .collect(Collectors.groupingBy(DistrictDocument::getCity,
                 Collectors.mapping(d->createFizz(d),Collectors.toList())));

If createFizz(d) would return a List<Fizz, you can flatten it using Java 9's Collectors.flatMapping:

Map<String, List<Fizz>> collect =
    docs.stream()
        .collect(Collectors.groupingBy(DistrictDocument::getCity,
                 Collectors.flatMapping(d->createFizz(d).stream(),Collectors.toList())));

If you can't use Java 9, perhaps using Collectors.toMap will help:

Map<String, List<Fizz>> collect =
    docs.stream()
        .collect(Collectors.toMap(DistrictDocument::getCity,
                                  d->createFizz(d),
                                  (l1,l2)->{l1.addAll(l2);return l1;}));
Eran
  • 387,369
  • 54
  • 702
  • 768
  • `d->createFizz(d)` You can use a method reference for this. Admittedly we don't know the class. – Michael Feb 28 '18 at 12:25
  • @Michael yes, not knowing the class was the main reason I didn't use a method reference. – Eran Feb 28 '18 at 12:26
  • Could you check my edit? Unfortunately createFizz has to return List and I can't seem to find a way to flat out this list. – doublemc Feb 28 '18 at 12:40
  • @doublemc I believe java 9 has Collectors.flatMapping, so you can write `Collectors.flatMapping(d->createFizz(d).stream(),Collectors.toList())))`; – Eran Feb 28 '18 at 12:41
  • 1
    What if I can only use up to java 8? Any option to do that? – doublemc Feb 28 '18 at 12:43
  • @doublemc You can use `Collectors.toMap` instead of `groupingBy`. – Eran Feb 28 '18 at 12:47
  • If you are on java 8, you can use the `flatMapping` [backport from this answer](https://stackoverflow.com/a/32254323/1876620). You can place that helper method in any utility class. – fps Feb 28 '18 at 15:21
  • Your last variant relies on `createFizz` to return new, mutable lists. – Holger Mar 01 '18 at 12:00
10

In case you'd like to do a doubly nested grouping:

Let's say you have a collection of EducationData objects that contain School name, teacher name, and student name. But you want a nested map that looks like :

What you have

class EducationData {
     String school;
     String teacher;
     String student;

     // getters setters ...
}

What you want

Map<String, Map<String, List<String>>> desiredMapOfMpas ...

// which would look like this :

"East High School" : {
     "Ms. Jackson" : ["Derek Shepherd", "Meredith Grey", ...],
     "Mr. Teresa" : ["Eleanor Shellstrop", "Jason Mendoza", ...],
     ....
}

How to Get There

import static java.util.stream.Collectors.*;

public doubleNestedGroup(List<EducationData> educations) {
     Map<String, Map<String, List<String>>> nestedMap = educations.stream()
        .collect(
             groupingBy(
                  EducationData::getSchool,
                  groupingBy(
                       EducationData::getTeacher,
                       mapping(
                            EducationData::getStudent,
                            toList()
                       )
                  )
              )
        );
}
Fabian
  • 3,310
  • 3
  • 26
  • 35