1.12 约简操作
reduce方法是一种用于从流中计算某个值的通用机制,其最简单的形式将接受一个二元函数,并从前两个元素开始持续应用它。如果该函数是求和函数,那么就很容易解释这种机制:
在上面的情况中,reduce方法会计算v0+v1+v2+…,其中vi是流中的元素。如果流为空,那么该方法会返回一个Optional,因为没有任何有效的结果。
注意:在上面的情况中,可以写成reduce(Integer::sum)而不是reduce((x, y) -> x+y)。
通常,如果reduce方法有一项约简操作op,那么该约简就会产生v0 op v1 op v2 op…,其中我们将函数调用op(vi, vi+1)写作vi op vi+1。这项操作应该是可结合的:即组合元素时使用的顺序不应该成为问题。在数学标记法中,(x op y) op z必须等于x op (y op z)。这使得在使用并行流时,可以执行高效的约简。
有很多种在实践中会显得很有用的可结合操作,例如求和、乘积、字符串连接、取最大值和最小值、求集的并与交等。减法是一个不可结合操作的例子,例如,(6-3)-2≠6-(3-2)。
通常,会有一个幺元值e使得e op x = x,可以使用这个元素作为计算的起点。例如,0是加法的幺元值。然后,可以调用第2种形式的reduce:
如果流为空,则会返回幺元值,你就再也不需要处理Optional类了。
现在,假设你有一个对象流,并且想要对某些属性求和,例如字符串流中的所有字符串的长度,那么你就不能使用简单形式的reduce,而是需要(T,T)->T这样的函数,即引元和结果的类型相同的函数。但是在这种情况下,你有两种类型:流的元素具有String类型,而累积结果是整数。有一种形式的reduce可以处理这种情况。
首先,你需要提供一种“累积器”函数(total, word) -> total + word.length()。这个函数会被反复调用,产生累积的总和。但是,当计算被并行化时,会有多个这种类型的计算,你需要将它们的结果合并。因此,你需要提供第二个函数来执行此处理。完整的调用如下:
注意:在实践中,你可能并不会频繁地用到reduce方法。通常,映射为数字流并使用其方法来计算总和、最大值和最小值会更容易。(我们将在1.13节中讨论数字流。)在这个特定示例中,你可以调用words.mapToInt(String::length).sum(),因为它不涉及装箱操作,所以更简单也更高效。
注意:有时reduce会显得并不够通用。例如,假设我们想要收集BitSet中的结果。如果收集操作是并行的,那么就不能直接将元素放到单个BitSet中,因为BitSet对象不是线程安全的。因此,我们不能使用reduce,因为每个部分都需要以其自己的空集开始,并且reduce只能让我们提供一个幺元值。此时,应该使用collect,它会接受单个引元:
1.一个提供者,它会创建目标类型的新实例,例如散列集的构造器。
2.一个累积器,它会将一个元素添加到一个实例上,例如add方法。
3.一个组合器,它会将两个实例合并成一个,例如addAll。
下面的代码展示了collect方法是如何操作位集的:
java.util.Stream 8