Flatten a map after Collectors.groupingBy in java

  • A+

I have list of students. I want to return list of objects StudentResponse classes that has the course and the list of students for the course. So I can write which gives me a map

Map<String, List<Student>> studentsMap = students.stream().             .collect(Collectors.groupingBy(Student::getCourse,                     Collectors.mapping(s -> s, Collectors.toList()              ))); 

Now I have to iterate through the map again to create a list of objects of StudentResponse class which has the Course and List. Is there a way to combine these two iterations?


Probably way overkill but it was a fun exercise :) You could implement your own Collector:

import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.*; import java.util.stream.Collector; import java.util.stream.Collectors;  public class StudentResponseCollector implements Collector<Student, Map<String, List<Student>>, List<StudentResponse>> {      @Override     public Supplier<Map<String, List<Student>>> supplier() {         return () -> new ConcurrentHashMap<>();     }      @Override     public BiConsumer<Map<String, List<Student>>, Student> accumulator() {         return (store, student) -> store.merge(student.getCourse(),                 new ArrayList<>(Arrays.asList(student)), combineLists());     }      @Override     public BinaryOperator<Map<String, List<Student>>> combiner() {         return (x, y) -> {             x.forEach((k, v) -> y.merge(k, v, combineLists()));              return y;         };     }      private <T> BiFunction<List<T>, List<T>, List<T>> combineLists() {         return (students, students2) -> {             students2.addAll(students);             return students2;         };     }      @Override     public Function<Map<String, List<Student>>, List<StudentResponse>> finisher() {         return (store) -> store                 .keySet()                 .stream()                 .map(course -> new StudentResponse(course, store.get(course)))                 .collect(Collectors.toList());     }      @Override     public Set<Characteristics> characteristics() {         return EnumSet.of(Characteristics.UNORDERED);     } } 

Given Student and StudentResponse:

public class Student {     private String name;     private String course;      public Student(String name, String course) {         this.name = name;         this.course = course;     }      public String getName() {         return name;     }      public String getCourse() {         return course;     }      public String toString() {         return name + ", " + course;     } }  public class StudentResponse {     private String course;     private List<Student> studentList;      public StudentResponse(String course, List<Student> studentList) {         this.course = course;         this.studentList = studentList;     }      public String getCourse() {         return course;     }      public List<Student> getStudentList() {         return studentList;     }      public String toString() {         return course + ", " + studentList.toString();     } } 

Your code where you collect your StudentResponses can now be very short and elegant ;)

public class StudentResponseCollectorTest {      @Test     public void test() {         Student student1 = new Student("Student1", "foo");         Student student2 = new Student("Student2", "foo");         Student student3 = new Student("Student3", "bar");          List<Student> studentList = Arrays.asList(student1, student2, student3);          List<StudentResponse> studentResponseList = studentList                 .stream()                 .collect(new StudentResponseCollector());          assertEquals(2, studentResponseList.size());     } } 


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: