使用Java 8过滤具有多个条件的流,直到结果不为空



我有一个名为Student的对象的ArrayList。我想过滤这个列表并返回一个单个Student对象仅基于层次条件.

条件如下:

  • 选择第一个COMP学生。
  • 如果没有返回值,请选择一个ECON学生。
  • 如果没有返回,请选择一个AGRI学生。
  • …等。

在我的设计中,我已经硬编码了这个-但也许它可以使用排序的SetList或其他东西。

我现在的代码可以工作,但是看起来很乱,如果引入更多的条件,它会变得相当大。

我需要Student filterStudent(List<Student> studentList)方法的帮助。如何在更少的代码行或方法链中做到这一点。以及如何引入Enums的排序Set,可以根据偏好进行过滤(例如,先COMP,如果为空,然后是ECON,等等)。如果找到,停止过滤)。

代码:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
class Student {
private final int stNumber;
private final String stModule;//...more fields
public Student(int stNumber, String stModule) {
this.stNumber = stNumber;
this.stModule = stModule;
}
public int getStNumber() {
return stNumber;
}
public String getStModule() {
return stModule;
}
@Override
public String toString() {
return "Student{" +
"stNumber=" + stNumber +
", stModule='" + stModule + ''' +
'}';
}
}
enum Module {
COMP("Computers"),
ECON("Economics"),
AGRI("Agriculture"),
PHYS("Physics"); //...very large list
private String moduleName;
public String getModuleName() {
return moduleName;
}
Module(String moduleName) {
this.moduleName = moduleName;
}
}
public class Question {
public static void main(String[] args) throws Exception {
//These lists can be any size and contain any information
List<Student> studentListOne = Arrays.asList(
new Student(41, "Economics"),
new Student(45, "Computers")
);
List<Student> studentListTwo = Arrays.asList(
new Student(11, "Physics"),
new Student(23, "Agriculture"),
new Student(86, "Physics"),
new Student(34, "Economics")
);
List<Student> studentListThree = new ArrayList<>();
System.out.println(filterStudent(studentListOne));
System.out.println(filterStudent(studentListTwo));
System.out.println(filterStudent(studentListThree)); //exception
}
/**
* Rules are: Choose the first instance of a COMP student.
* If the list is empty, choose a ECON student.
* If the list is still empty, choose a AGRI student.
*
* @param studentList
* @return Student
*/
public static Student filterStudent(List<Student> studentList) throws Exception {
Optional<Student> selectedStudent;
selectedStudent = studentList.stream().filter(student -> student.getStModule().equalsIgnoreCase(Module.COMP.getModuleName())).findFirst();
if (selectedStudent.isPresent()) {
return selectedStudent.get();
}
selectedStudent = studentList.stream().filter(student -> student.getStModule().equalsIgnoreCase(Module.ECON.getModuleName())).findFirst();
if (selectedStudent.isPresent()) {
return selectedStudent.get();
}
selectedStudent = studentList.stream().filter(student -> student.getStModule().equalsIgnoreCase(Module.AGRI.getModuleName())).findFirst();
if (selectedStudent.isPresent()) {
return selectedStudent.get();
}
throw new Exception("No student found matching criteria");
}
}

你可以用一个循环重写当前的方法:

for (Module mod : Arrays.asList(Module.COMP, Module.ECON, Module.AGRI)) {
String subj = mod.getModuleName();
Optional<Student> selectedStudent = studentList.stream().filter(student -> student.getStModule().equalsIgnoreCase(subj)).findFirst();
if (selectedStudent.isPresent()) {
return selectedStudent.get();
}
}
throw new Exception(...);

如果你想避免列表的多次迭代,你可以声明一个Comparator<Student>:

Map<String, Integer> ordering = Map.of(Module.COMP.getModuleName().toLowerCase(), 0, Module.ECON.getModuleName().toLowerCase(), 1, Module.AGRI.getModuleName().toLowerCase(), 2);
ToIntFunction<Student> studentOrdering = student -> ordering.getOrDefault(student.getModuleName().toLowerCase(), ordering.size());
Comparator<Student> comparator = Comparator.nullsLast(Comparator.comparingInt(studentOrdering));
一个简单的解决方案就是使用Stream.min:
Optional<Student> student = studentList.stream().min(comparator);

,这可能很好。如果你想用一个更明确的列表来做,你可以在你找到一个COMP学生后立即停止:

Student best = null;
for (Student student : studentList) {
int comp = comparator.compare(best, student);
if (comp > 0) {
best = student;
int studentOrder = studentOrdering.apply(student);
if (studentOrder == 0) {
// Can't find a better student, so fast break.
break;
}
}
}
if (best != null) {
return best;
}
throw new Exception(...);

要支持动态列表和所选模块的顺序,您可以做的一件事是过滤列表,对其排序,然后选择第一个元素:

public static Student filterStudent(List<Student> studentList, List<String> moduleOrder)
throws Exception {
return studentList.stream().filter(s -> moduleOrder.contains(s.getStModule()))
.min(Comparator.comparingInt(student -> moduleOrder.indexOf(student.getSModule())))
.orElseThrow(() -> new Exception("No student found matching criteria"));
}

为了执行当前的逻辑,您可以使用学生列表和List.of(Module.COMP.getModuleName(), Module.ECON.getModuleName(), Module.AGRI.getModuleName())作为第二个参数来调用它。

这将避免在列表上重复迭代。但我假设moduleOrder列表很小,与您的示例中一样只有少数元素(它被搜索了几次以进行过滤和排序)

最新更新