3.13 惰性流
问题
用户希望处理满足条件所需的最小数量的流元素。
方案
流是惰性的,在达到终止条件前不会处理元素,达到终止条件后逐个处理每个元素。如果遇到短路操作,那么只要满足所有条件,流处理就会终止。
讨论
读者在第一次接触流处理时,很容易认为流处理的效率不高。如例 3-657 所示,我们将 100 到 200 之间的所有整数倍增,然后找出能被 3 整除的第一个整数。
7感谢 Venkat Subramaniam 博士为本例所做的贡献。
例 3-65 将 100 到 200 之间的所有整数倍增,再找出能被 3 整除的第一个整数
OptionalInt firstEvenDoubleDivBy3 = IntStream.range(100, 200).map(n -> n * 2).filter(n -> n % 3 == 0).findFirst();System.out.println(firstEvenDoubleDivBy3); ➊
➊ 打印 Optional[204]
如果不了解流处理的机制,读者可能认为上述代码做了不少无用功:
- 创建 100 到 199 之间的整数(100 次操作)
- 将每个整数倍增(100 次操作)
- 校验每个整数能否被 3 整除(100 次操作)
- 返回结果流的第一个元素(1 次操作)
那么,既然满足要求的第一个值为 204,为什么还要处理所有其他的数字呢?
不要误会,流处理的机制并非如此。流是惰性的,在达到终止条件前不会处理元素,达到终止条件后才通过流水线逐一处理每个元素。为说明这一点,例 3-66 对代码进行重构,以便读者观察每个元素通过流水线时的情况。
例 3-66 对每个流元素进行显式处理
- public int multByTwo(int n) { ➊
- System.out.printf("Inside multByTwo with arg %d%n", n);
- return n * 2;
- }
- public boolean divByThree(int n) { ➋
- System.out.printf("Inside divByThree with arg %d%n", n);
- return n % 3 == 0;
- }
- // 其他代码
- firstEvenDoubleDivBy3 = IntStream.range(100, 200)
- .map(this::multByTwo) ➊
- .filter(this::divByThree) ➋
- .findFirst();
❶ 用于倍增(并打印)的方法引用
❷ 用于对 3 取模(并打印)的方法引用
例 3-66 的输出如下:
Inside multByTwo with arg 100Inside divByThree with arg 200Inside multByTwo with arg 101Inside divByThree with arg 202Inside multByTwo with arg 102Inside divByThree with arg 204First even divisible by 3 is Optional[204]
在本例中,100 被映射到 200,它未通过筛选器,流移至 101;101 被映射到 202,它同样未通过筛选器;下一个值 102 被映射到 204,它能被 3 整除,因此通过筛选器。流处理在仅处理三个值后即告终止,一共进行了 6 次操作。
这是流处理相对于直接处理集合的最大优点之一。对集合而言,必须执行完所有操作才能进行下一步操作。对流而言,各种中间操作构成了一条流水线,但流在达到终止操作前不会处理任何元素,达到终止操作后只处理所需的值。
流处理并非在任何情况下都有意义:如果进行任何状态操作(如排序或求和),就不得不处理所有值。但是,如果无状态操作后跟一个短路终止操作,流处理的优点还是很明显的。
另见
有关 findFirst 和 findAny 方法之间的差异请参见范例 3.9。
