在Collectors.groupingBy之后将地图展平

fas*_*ava 5 java collections java-stream

我有学生名单.我想返回具有课程的对象列表StudentResponse课程以及课程的学生列表.所以我可以写一下给我一张地图

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

现在我必须再次遍历地图以创建StudentResponse具有课程和列表的类对象 列表:

class StudentResponse {
     String course;
     Student student;

     // getter and setter
}
Run Code Online (Sandbox Code Playgroud)

有没有办法结合这两个迭代?

Fed*_*ner 3

不完全是您所要求的,但为了完整起见,这是一种完成您想要的事情的紧凑方法:

Map<String, StudentResponse> map = new LinkedHashMap<>();
students.forEach(s -> map.computeIfAbsent(
        s.getCourse(), 
        k -> new StudentResponse(s.getCourse()))
    .getStudents().add(s));
Run Code Online (Sandbox Code Playgroud)

这假设StudentResponse有一个构造函数,它接受课程作为参数和学生列表的 getter,并且该列表是可变的(即ArrayList),以便我们可以将当前学生添加到其中。

虽然上述方法有效,但它显然违反了面向对象的基本原则,即封装。如果您对此表示同意,那么您就完成了。如果您想遵循封装,那么您可以添加一个方法来StudentResponse添加Student实例:

public void addStudent(Student s) {
    students.add(s);
}
Run Code Online (Sandbox Code Playgroud)

那么,解决方案将变成:

Map<String, StudentResponse> map = new LinkedHashMap<>();
students.forEach(s -> map.computeIfAbsent(
        s.getCourse(), 
        k -> new StudentResponse(s.getCourse()))
    .addStudent(s));
Run Code Online (Sandbox Code Playgroud)

该解决方案显然比前一个解决方案更好,并且可以避免严重的代码审查者的拒绝。

两种解决方案都依赖于Map.computeIfAbsent,它要么返回StudentResponse所提供课程的 a (如果地图中存在该课程的条目),要么创建并返回StudentResponse使用该课程作为参数构建的实例。然后,该学生将被添加到返回的学生的内部列表中StudentResponse

最后,您的StudentResponse实例位于映射值中:

Collection<StudentResponse> result = map.values();
Run Code Online (Sandbox Code Playgroud)

如果您需要 aList而不是 a Collection

List<StudentResponse> result = new ArrayList<>(map.values());
Run Code Online (Sandbox Code Playgroud)

注意:我使用LinkedHashMap而不是HashMap保留插入顺序,即原始列表中学生的顺序。如果没有这样的需求,直接使用即可HashMap