我应该在一个循环中使用多个流吗?

Should I use several streams o a single loop?

假设我们正在使用这个 class:

public Student{
  String name;
  Integer age;
  Integer height;
  Integer weight;
}

现在我们有了一份学生名单,我们会被问到类似这样的问题:

我认为一个干净易懂的解决方案是使用 lambda 按名称过滤并获取所要求的内容:

List<Student> students = ...
double jonhAge = students.stream().filter(s->s.getName().equals("Jonh").mapToInt(s->s.getAge()).average()
double maryHeight = students.stream().filter(s->s.getName().equals("Mary").mapToInt(s->s.getHeight()).max()
double benWeight = students.stream().filter(s->s.getName().equals("Ben").mapToInt(s->s.getWeight()).min()

我想至少它在列表上迭代了 3 次,并且使用带有条件的单个循环可能更有效:

double jonhAge = 0;
double jonhCount = 0;
double maryHeight = 0;
double benWeight = 1000;
for(Student s : students){
  if(s.getName.equals("Jonh")){
    jonhAge += s.getAge();
    jonhCount++;
  }else if(s.getName.equals("Mary")){
    if(s.getHeight()>maryHeight)
      maryHeight = s.getHeight();
  }else if(s.getName.equals("Ben")){
    if(s.getWeight()<benWeight )
      benWeight = s.getWeight();
  }
}
jonhAge = jonhAge / jonhCount;

我认为第一种方式更简洁,但第二种方式更快。我对吗?我应该使用哪一个? 假设 students 列表包含大量元素,名字的数量比 Jonh、Mary 和 Ben 还要多。

我认为您正在考虑正确的问题。您当然可以尝试循环以避免多次传递同一数据流。然而,还有更多有趣的流式方法可以做到这一点。

List<String> goodNames = ...put in names you want to see...;
Map<String, List<Student>> postsPerName = students.stream().filter(goodNames::contains).collect(groupingBy(Student::getName));

然后你就有了一张地图,其中只有你关心的名字和所有与其名字匹配的元素。

这是“完美”的解决方案吗?也许,也许不是。但这一切都是平衡的。祝你好运!

这可以通过 Stream API 以下列方式实现:

  1. 创建名称映射到 Student class
  2. 的适当吸气剂
  3. IntSummaryStatistics class
  4. 的吸气剂创建另一个名称映射
  5. Collectors.groupingBy + Collectors.summarizingInt 构建一个地图以获取每个学生的统计数据,并使用 Collectors.toMap 转换此地图以获取学生所需的统计参数。

更新
可以实施枚举以存储对 StudentIntSummaryStatistics 中适当吸气剂的引用,然后映射应使用枚举值。

enum FieldStat {
    AGE_AVERAGE(Student::getAge, IntSummaryStatistics::getAverage),
    HEIGHT_MAX(Student::getHeight, IntSummaryStatistics::getMax),
    WEIGHT_MIN(Student::getWeight, IntSummaryStatistics::getMin);

    private final ToIntFunction<Student> field;
    private final Function<IntSummaryStatistics, Number> stat;

    FieldStat(ToIntFunction<Student> getter, Function<IntSummaryStatistics, Number> stat) {
        this.field = getter;
        this.stat = stat;
    }

    public ToIntFunction<Student> getField() {
        return field;
    }

    public Function<IntSummaryStatistics, Number> getStat() {
        return stat;
    }
}

示例实现:

List<Student> students = Arrays.asList(
    new Student("John", 35, 177, 78), new Student("John", 29, 173, 72),
    new Student("Mary", 32, 164, 58), new Student("Mary", 28, 167, 56),
    new Student("Ben",  24, 181, 84), new Student("Ben",  27, 169, 65),
    new Student("Bob",  35, 178, 80)
);

Map<String, FieldStat> mapStat = Map.of(
        "John", FieldStat.AGE_AVERAGE,
        "Mary", FieldStat.HEIGHT_MAX,
        "Ben",  FieldStat.WEIGHT_MIN
);

Map<String, Number> stats = students.stream()
    .filter(st -> mapStat.containsKey(st.getName()))
    .collect(Collectors.groupingBy(
        Student::getName,
        Collectors.summarizingInt(st -> mapStat.get(st.getName()).getField().applyAsInt(st))
    ))
    .entrySet().stream()
    .collect(Collectors.toMap(
        Map.Entry::getKey, 
        e -> mapStat.get(e.getKey()).getStat().apply(e.getValue())
    ));

System.out.println(stats);

输出

{John=32.0, Ben=65, Mary=167}