1. 概述

在这个教程中,我们将探讨Java流的不同过滤方式。首先,我们将关注哪种方法能产生更易读的代码。然后,我们将从性能角度来比较这些解决方案。

2. 可读性

2.1. 多个过滤器

流API允许链式应用多个过滤器。我们可以利用这一点来满足复杂过滤条件。如果需要否定条件,我们还可以使用not谓词。这种方法将使代码清晰易懂:

public class Student {

    private String name;
    private int year;
    private List<Integer> marks;
    private Profile profile;

    // constructor getters and setters

}

2.2. 单个复杂条件的过滤

另一种选择是使用一个包含复杂条件的单一过滤器。不幸的是,这会导致代码阅读起来稍显困难:

@Test
public void whenUsingSingleComplexFilter_dataShouldBeFiltered() {
    List<Student> filteredStream = students.stream()
      .filter(s -> s.getMarksAverage() > 50 
        && s.getMarks().size() > 3 
        && s.getProfile() != Student.Profile.PHYSICS)
      .collect(Collectors.toList());

    assertThat(filteredStream).containsExactly(mathStudent);
}

尽管如此,我们可以将条件提取到单独的方法中以改进可读性:

public boolean isEligibleForScholarship() {
    return getMarksAverage() > 50
      && marks.size() > 3
      && profile != Profile.PHYSICS;
}

这样,我们将复杂的条件隐藏起来,让过滤标准更具意义:

@Test
public void whenUsingSingleComplexFilterExtracted_dataShouldBeFiltered() {
    List<Student> filteredStream = students.stream()
        .filter(Student::isEligibleForScholarship)
        .collect(Collectors.toList());

    assertThat(filteredStream).containsExactly(mathStudent);
}

当能将过滤逻辑封装在模型中时,这是一个理想的解决方案。

3. 性能

虽然多过滤器提高了代码的可读性,但会增加对象创建,可能影响性能。为了验证这一点,我们将过滤不同大小的流,并对元素进行多次检查。然后计算总处理时间并比较两种方法。测试还包括并行流和传统的for循环:

(图片缺失,无法展示图表)

结果显示,使用复杂条件会带来性能提升。但对于小样本量,差异可能不明显。

4. 条件的执行顺序

无论使用单个还是多个过滤器,如果检查顺序不当,过滤操作可能会导致性能下降。

4.1. 过滤掉大量元素的条件

假设我们有一个包含100个整数的流,想要找到小于20的偶数。如果首先检查奇偶性,我们将进行总共150次检查,因为每次都要评估第一个条件,而第二个条件只针对偶数。

@Test
public void givenWrongFilterOrder_whenUsingMultipleFilters_shouldEvaluateManyConditions() {
    long filteredStreamSize = IntStream.range(0, 100).boxed()
      .filter(this::isEvenNumber)
      .filter(this::isSmallerThanTwenty)
      .count();

    assertThat(filteredStreamSize).isEqualTo(10);
    assertThat(numberOfOperations).hasValue(150);
}

相反,如果颠倒过滤条件的顺序,我们只需要120次检查就能正确过滤流。因此,应优先评估那些过滤掉大部分元素的条件。

4.2. 慢速或耗时条件

有些条件可能较慢,比如其中一个过滤器涉及执行沉重的逻辑或网络外部调用。为了优化性能,我们将尽可能减少这些条件的评估次数。基本上,应在其他所有条件都满足时才评估这些条件。

5. 总结

在这篇文章中,我们分析了Java流的不同过滤方式。从可读性的角度看,多个过滤器提供了更直观的过滤条件。从性能方面来说,使用复杂条件(从而减少对象创建)将带来整体更好的性能。

如常,源代码可在GitHub上获取。