我有学生名单。 我想返回具有该课程的对象StudentResponse类的列表以及该课程的学生列表。 所以我可以写给我一张地图
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
}
有没有办法组合这两个迭代?
答案 0 :(得分:3)
可能是过度矫kill,但这是一个有趣的练习:)您可以实现自己的收集器:
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();
}
}
您用来收集StudentRespons的代码现在可能非常简短;)
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());
}
}
答案 1 :(得分:3)
并非完全是您的要求,但这是一种完成您想要的内容的紧凑方法,只是为了保持完整性:
Map<String, StudentResponse> map = new LinkedHashMap<>();
students.forEach(s -> map.computeIfAbsent(
s.getCourse(),
k -> new StudentResponse(s.getCourse()))
.getStudents().add(s));
这里假设StudentResponse
有一个构造函数,该构造函数接受该课程作为参数,并且是学生列表的获取器,并且该列表是可变的(即ArrayList
),以便我们可以添加当前学生
虽然上述方法有效,但显然违反了基本的面向对象原理,即封装。如果您对此表示满意,那么您就完成了。如果要遵守封装,则可以向StudentResponse
添加方法以添加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
,后者要么为提供的课程返回StudentResponse
(如果在地图中存在该课程的条目),要么创建并返回StudentResponse
以课程为参数构建的实例。然后,将学生添加到返回的StudentResponse
的内部学生列表中。
最后,您的StudentResponse
实例在地图值中:
Collection<StudentResponse> result = map.values();
如果您需要List
而不是Collection
:
List<StudentResponse> result = new ArrayList<>(map.values());
注意:我使用LinkedHashMap
而不是HashMap
来保留插入顺序,即原始列表中学生的顺序。如果您没有这样的要求,只需使用HashMap
。
答案 2 :(得分:2)
首先,您的下游收集器(mapping
)是多余的,因此可以通过使用groupingBy
重载而不使用下游收集器来简化代码。
以List<T>
作为源,在使用groupingBy
重载(仅使用分类器)之后,结果映射为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())));
collectingAndThen
基本上是:
适应收集器以执行其他整理转换。
答案 3 :(得分:2)
只需遍历条目集并将每个条目映射到StudentResponse
:
List<StudentResponse> responses = studentsMap.entrySet()
.stream()
.map(e -> new StudentResponse(e.getKey(), e.getValue()))
.collect(Collectors.toList());
答案 4 :(得分:2)
这可以使用jOOλ库及其Seq.grouped
方法以非常简洁的方式完成:
carImg = pygame.image.load("racecar.png")
假定List<StudentResponse> responses = Seq.seq(students)
.grouped(Student::getCourse, Collectors.toList())
.map(Tuple.function(StudentResponse::new))
.toList();
具有构造函数StudentResponse
,并使用以下Tuple.function
重载转发到该构造函数。
答案 5 :(得分:1)
从my other answer到shmosel's answer都可以看到,最终您将需要调用studentsMap.entrySet()
将结果映射中的每个Entry<String, List<String>>
映射到{{1 }}对象。
您可以采用的另一种方法是toMap
方式;即
StudentResponse
这实际上与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
映射到Student
,最后在StudentResponse
函数中在发生键冲突的情况下使用merge
。
以上内容与StudentResponse::merge
类有关,该类至少具有以下字段,构造函数和方法:
StudentResponse