代码之家  ›  专栏  ›  技术社区  ›  fastcodejava

在java中的Collectors.groupingBy之后展平地图

  •  5
  • fastcodejava  · 技术社区  · 7 年前

    我有学生名单。 这样我就能写一张地图了

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

    现在,我必须再次遍历映射以创建 StudentResponse 包含课程和列表的类:

    class StudentResponse {
         String course;
         Student student;
    
         // getter and setter
    }
    

    有没有办法将这两个迭代组合起来?

    6 回复  |  直到 7 年前
        1
  •  3
  •   fps    7 年前

    这不完全是你所要求的,但这里有一个简洁的方法来完成你想要的,只是为了完整:

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

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

    尽管上述方法有效,但它显然违反了一个基本的OO原则,即封装。如果你同意,那么你就完了。如果您想尊重封装,那么可以向 学生反应 添加 Student 实例:

    public void addStudent(Student s) {
        students.add(s);
    }
    

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

    此解决方案明显优于前一个解决方案,可以避免严肃的代码审阅者拒绝。

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

    最后,你的 学生反应

    Collection<StudentResponse> result = map.values();
    

    List 而不是 Collection :

    List<StudentResponse> result = new ArrayList<>(map.values());
    

    注意:我正在使用 LinkedHashMap HashMap 哈希图 .

        2
  •  3
  •   Jonck van der Kogel    7 年前

    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);
        }
    }
    

    给定学生和学生回答:

    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();
        }
    }
    

    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());
        }
    }
    
        3
  •  2
  •   shmosel    7 年前

    StudentResponse :

    List<StudentResponse> responses = studentsMap.entrySet()
            .stream()
            .map(e -> new StudentResponse(e.getKey(), e.getValue()))
            .collect(Collectors.toList());
    
        4
  •  2
  •   Ousmane D.    7 年前

    首先,您的下游收集器( mapping groupingBy 没有下游收集器时过载。

    给予 List<T> 分组依据 Map<K, List<T>> 因此可以避免映射操作。

    至于你的问题,你可以用 collectingAndThen :

    students.stream()
            .collect(collectingAndThen(groupingBy(Student::getCourse), 
                       m -> m.entrySet()
                            .stream()
                            .map(a -> new StudentResponse(a.getKey(), a.getValue()))
                            .collect(Collectors.toList())));
    

    收集然后 基本上:

        5
  •  2
  •   Tomasz Linkowski    7 年前

    可以使用 jOOλ Seq.grouped 方法:

    List<StudentResponse> responses = Seq.seq(students)
            .grouped(Student::getCourse, Collectors.toList())
            .map(Tuple.function(StudentResponse::new))
            .toList();
    

    它假定 StudentResponse 有一个构造函数 StudentResponse(String course, List<Student> students) ,并使用以下命令转发到此构造函数 Tuple.function

        6
  •  1
  •   Ousmane D.    7 年前

    你可以从中看到 my other answer 以及 shmosel's answer ,您最终需要调用 studentsMap.entrySet() 映射 Entry<String, List<String>> 在生成的映射中 StudentResponse

    toMap 方式;即

    Collection<StudentResponse> result = students.stream()
                    .collect(toMap(Student::getCourse,
                            v -> new StudentResponse(v.getCourse(),
                                    new ArrayList<>(singletonList(v))),
                            StudentResponse::merge)).values();
    

    这本质上是将 Student 反对他们的路线( Student::getCourse )就像 groupingBy 收藏家;然后在 valueMapper 函数映射自 merge StudentResponse::merge 在关键点碰撞的情况下。

    学生反应

    class StudentResponse {
        StudentResponse(String course, List<Student> students) {
            this.course = course;
            this.students = students;
        }
    
        private List<Student> getStudents() { return students; }
    
        StudentResponse merge(StudentResponse another){
            this.students.addAll(another.getStudents());
            // maybe some addition merging logic in the future ...
            return this;
        }
    
        private String course;
        private List<Student> students;
    }