目前网上的翻译机翻感都很重,于是博主自己进行了翻译。如有错漏或疑问,欢迎提出~
模块 java.base
Stream支持对流元素进行函数式操作。例如,对集合执行map-reduce(映射-缩减)转换。例如:
int sum = widgets.stream()
.filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();
在这个例子中,我们使用的流源是widgets,即Collection<Widget>。接下来,对流执行filter-map-reduce操作,以获得red widgets的权重之和。注意,求和是一种缩减操作(reduction)。
Stream包引入了一个重要的抽象概念——流。根据元素类型,流可以分为两大类:一类是对象流,包含Stream接口,元素是原始数据类型的包装类;另一类是数值流(又名原始类型特化流),包含IntStream、LongStream和DoubleStream接口,流元素分别是原始数据类型int、long和double。
流与集合这两个概念可能容易混淆。二者的不同主要体现在以下几方面:
- 流不能存储元素。流并不是存储元素的数据结构;相反,它会从数据结构、数组、生成器函数或I/O通道等数据源中获取元素,然后将元素传递到流管道中进行流计算。
- 流是函数式的。流操作会产生结果,但不会修改流源。例如,当一个流的流源是集合时,执行过滤操作将创建一个不含有被过滤元素的新流,而不是在原集合中删除元素。
- 流操作会延迟执行。流操作分为两类,分别是创建新流的中间操作,以及产生结果或副作用的终端操作。中间操作都是延迟执行的,例如过滤、映射或去重操作,以便于优化操作流程。举个例子,如果你想要“查找第一个含有三个连续元音的字符串”,并不需要检查所有的输入字符串。
- 流可能是无限的。集合的大小是有限的,流却并非如此。无限流的短路操作可以在有限时间内执行完毕,例如limit(n)或findFirst()。
- 流会被消耗。在流的生命周期中,只能访问一次流元素。如果你想要使用 Iterator再次访问同一个数据源的元素,就必须先生成一个新流。
我们可以通过多种方式获得流。例如:
- 通过stream()和parallelStream()方法从集合(Collection)中获得流;
- 通过Arrays.stream(Object[])从数组中获得流;
- 通过stream类的静态工厂方法获得流,例如Stream.of(Object[]) 、IntStream.range(int, int)或Stream.iterate(Object, UnaryOperator);
- 通过BufferedReader.lines()按行读取文档;
- 通过Files中的方法获得文件路径流;
- 通过Random.ints()获得随机数流;
- JDK中还有许多基于流的方法,包括BitSet.stream()、 Pattern.splitAsStream(java.lang.CharSequence)和JarFile.stream()。
此外,第三方库也可以提供流源,具体方法请参考these techniques。
流操作和流管道
流操作分为中间操作和终端操作,二者结合形成流管道。流管道包含以下几部分:首先是数据源,例如集合、数组、生成器函数或I/O通道;然后是零个或多个中间操作,例如Stream.filter或Stream.map;以及终端操作,例如Stream.forEach或Stream.reduce。
中间操作会返回一个新流,并且总是延迟执行。例如,在执行中间操作filter()时,并不会立刻过滤调用流的元素,而是先创建一个新流;等到遍历时,新流包含了与给定的谓词相匹配的调用流元素。注意,直到调用终端操作时,遍历操作才会开始执行。
终端操作也许会遍历流,以产生结果或副作用,例如Stream.forEach或IntStream.sum操作。终端操作执行后,流管道被消耗了,因此无法再次使用;如果你想要再次遍历同一数据源,就必须重返数据源来获取一个新流。几乎在所有情况下,终端操作都是紧急操作;也就是说,在返回结果之前,终端操作已经完成了对数据源的遍历与管道的处理。只有iterator()和spliterator()是例外,因为这两个终端操作是“应急出口”——在现有操作不足以完成任务的情况下,允许程序员根据自己的需求再次对管道进行遍历。
延迟执行能够显著提高效率。例如,前面的filter-map-sum例子中,filter和map这两个中间操作在调用终端操作sum时才开始执行。只需传递一次数据,中间状态极小,效率也更高。此外,延迟执行可以避免在不必要的情况下检查所有的数据。如果你想要“找到第一个超过1000 个字符的字符串”,只需检查刚好足够多的字符串,而不必检查所有的字符串。(当输入流极大或者输入流是无限流时,延迟执行尤为重要。)
中间操作进一步划分为无状态操作和有状态操作。在处理新元素时,无状态操作(例如filter和map)不会保留先前元素的状态——每个元素的处理都独立于其他元素;而有状态的操作(例如distinct和sorted)可能会将先前元素的状态与新元素的状态相结合。
对有状态的操作而言,可能要等到处理完所有输入后才会产生结果。例如,只有查看了所有的元素之后,才能对流进行排序并产生结果。因此,如果并行计算的流管道包含了有状态的中间操作,可能需要多次传递数据,或者缓冲重要的数据。相比之下,如果流管道只包含了无状态中间操作,那么无论是串行还是并行计算,都只需要传递一次数据,数据缓冲也极少。
此外,还有一类操作被称为短路操作。在无限输入的情况下,如果中间操作可能会产生一个有限流,那么该操作就是短路操作;相同情况下,如果终端操作可能在有限时间内终止,那么该操作也是短路操作。要想在有限时间内正常终止一个无限流,在管道中执行短路操作是必要条件,但并非充分条件。
并行性
for循环本质上是串行的。相比之下,流将“计算”定义为“由聚合操作构成的管道”,而不是对“对每个元素的命令式操作”,以此推动并行操作。流操作既可以是串行的,也可以是并行的。除非指定为并行流,否则JDK中的流实现默认创建串行流。例如,集合中的Collection.stream()和Collection.parallelStream()方法分别用于创建串行流和并行流;而基于流的方法(例如IntStream.range(int, int))所创建的串行流,可以调用BaseStream.parallel()方法将自身并行化。例如,想要并行查询上文提到的“widgets的权重之和”,我们可以这样做:
int sumOfWeights = widgets.parallelStream()
.filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();
对这个例子而言,并行和串行的唯一区别在于——创建初始流时使用的是“parallelStream()”还是“stream()”。因为调用终端操作的流决定了流管道的执行模式。我们可以使用BaseStream.isParallel()方法来确定调用流是串行还是并行的,也可以使用BaseStream.sequential()和BaseStream.parallel()方法改变调用流的执行模式。最新版本中,串行或并行模式的设置作用于整个流管道。
除了非确定性的操作之外(例如findAny()),串行或并行执行不会改变流计算的结果。
大多数的流操作接受参数的传入。这些参数通常是lambda表达式,描述了由用户指定的行为。为了确保行为的正确执行,行为参数必须是无干扰的,并且在大多数情况下必须是无状态的。这些参数一定是函数式接口(functional interface)的实例,例如Function;并且通常是lambda表达式或方法引用。
无干扰
流允许我们对多种类型的数据源执行并行的聚合操作,甚至包括ArrayList等非线程安全的集合。要想实现这点,必须防止数据源在流管道执行期间被干扰。除了iterator()和spliterator()这两个应急出口操作外,流管道的执行都始于终端操作的调用,终于终端操作的结束。对于大部分的数据源来说,防干扰就是要确保数据源在流管道执行期间不会被修改。不过,当数据源是并发集合时情况则不同,因为并发集合是专门用于处理并发修改的集合。当流的分割迭代器显示CONCURRENT特性时,其流源就是并发流源。
因此,对于非并发的流源而言,流管道的行为参数不可以修改流源。反之,如果行为参数能修改流源或导致流源被修改,这就是在干扰非并发的流源。防干扰的需求并非独属于并行管道,而是所有流管道的需求。在流管道执行期间修改非并发的流源,可能会抛出异常、导致不正确的结果,或出现不一致的行为。可是,如果我们想要修改流源,那该怎么做呢?只要流源的行为良好,我们就可以在终端操作开始前对流源进行修改。这样一来,这些改动会如实反映在元素中。例如,请思考下面的代码:
List<String> l = new ArrayList(Arrays.asList(“one”, “two”));
Stream<String> sl = l.stream();
l.add(“three”);
String s = sl.collect(joining(“ ”));
首先,创建一个由字符串“one”和“two”组成的列表。然后,使用该列表创建一个流。接下来修改列表,添加第三个字符串“three”。最后,收集并组合流元素。因为在终端操作collect开始执行前已经修改了列表,所以结果是字符串“one two three”。当我们提前修改流源时,所有从JDK集合返回的流和大多数从JDK类返回的流都是行为良好的;至于那些由其他库生成的流,请参考底层流的构建(Low-level stream construction),进一步了解如何构建行为良好的流。
无状态行为
如果流操作的行为参数是有状态的,那么流管道的结果可能是不确定或不正确的。对于有状态的lambda(或其他执行函数式接口的对象)而言,最后的结果取决于流管道执行期间状态的改变。下面的示例中,map()的参数就是一个有状态的lambda:
Set<Integer> seen = Collections.synchronizedSet(new HashSet<>());
stream.parallel().map(e -> { if (seen.add(e)) return 0; else return e; })...
如果map操作是并行执行的,由于线程调度的不同,相同的输入会产生不同的结果;相反,如果是无状态的lambda表达式,产生的结果将始终相同。
注意,出于安全和性能方面的考虑,通过行为参数去访问可变的状态是个糟糕的选择:如果你并未同步对该状态的访问,数据竞争会破坏你的代码;不过,即便同步了访问,竞争带来的风险也远高于并行性带来的好处。因此,最佳选择就是避免有状态的行为参数出现在流操作中。通常,我们可以通过重新构建流管道来实现这点。
副作用
通常不建议在流操作中使用有副作用的行为参数,因为副作用常常会无意中违反无状态的要求,还会带来其他危害线程安全的隐患。
除非明确指定,否则有副作用的行为参数无法确保:
- 副作用是否对其他线程可见(visibility);
- 当多个操作应用于同一流管道中的“同一”元素时,是否是在同一线程中执行的;
- 行为参数是否一直处于被调用的状态。这是因为,如果能证明省略某个操作(或整个阶段)不会影响结果,那么流在执行时可以将其省略。
副作用的顺序也许会令人惊讶。即使流管道的结果只能和流源的相遇顺序保持一致,例如,IntStream.range(0,5).parallel().map(x -> x*2).toArray( )的结果一定是[0, 2, 4, 6, 8],也无法确保mapper函数应用于单个元素的顺序,或行为参数在哪个线程中执行给定的元素。
副作用的消失可能也出乎意料。除了forEach 和 forEachOrdered这两个终端操作外,如果某个行为参数的执行不会对结果产生影响,那么流实现可以省略该行为参数;因此,行为参数的副作用也许会随之消失。(具体示例请参考count 操作的API Note。)
相比有副作用的操作,使用无副作用的操作会更加安全、高效,例如用缩减操作(reduction)替代可变累加器。不过,出现在调试中的副作用通常是无害的,例如println()。此外,还有少数的流操作要通过副作用才能实现,例如forEach()和peek();因此,在使用此类操作时,应小心谨慎。
下面的例子展示了如何把有副作用的流管道转换为无副作用的流管道。在字符串流中寻找与给定的正则表达式相匹配的字符串,并将结果放入列表中。
ArrayList<String> results = new ArrayList<>();
stream.filter(s -> pattern.matcher(s).matches())
.forEach(s -> results.add(s)); //在非必要的情况下,使用了有副作用的操作!
在这个例子中,副作用没有存在的必要。如果并行执行,ArrayList的非线程安全性会导致不正确的结果;即便同步状态,也会带来竞争,从而削弱了并行的优点。在这段代码中,没有必要使用有副作用的操作forEach();因此,可以将该操作替换为更安全、更高效且更适合并行化的缩减操作collect():
List<String>results =
stream.filter(s -> pattern.matcher(s).matches())
.collect(Collectors.toList()); // 无副作用!
顺序
流是否定义了相遇顺序,这取决于流源和中间操作。有的流源本质上是有序的,例如列表或数组;有的流源则不是,例如HashSet。某些中间操作可以给无序流添加相遇顺序,例如sorted();还有一些中间操作会将有序流变为无序流,例如BaseStream.unordered()。此外,有的终端操作可能会忽略相遇顺序,例如forEach()。
如果流是有序的,大多数操作一定会按照相遇顺序执行。例如,流源是一个由[1, 2, 3]组成的列表,那么map(x -> x*2)操作的结果一定是[2, 4, 6]。但是,如果流源没有相遇顺序,那么无论[2, 4, 6]如何排列,都是正确的结果。
对于串行流而言,相遇顺序是否存在并不会影响执行的性能,只会影响结果的确定性。如果流是有序的,重复执行来自同一流源的同一流管道会产生相同的结果;如果流是无序的,重复执行则可能会产生不同的结果。
对于并行流而言,某些情况下放宽排序约束可以提高执行效率。要想确保合理的排序,某些操作(例如limit())可能需要缓冲,从而削弱了并行执行的优点。如果不考虑元素的顺序,某些聚合操作可以更高效地执行,例如过滤重复元素(distinct())或分组缩减(Collectors.groupingBy())。因此,当流的相遇顺序无关紧要时,可以使用unordered()消除相遇顺序。这样做也许能提高有状态的操作或终端操作的并行性能。不过,正如上面的“sum of weight of blocks”例子所示,即使有排序约束,大多数流管道仍能高效地并行化。
缩减操作
缩减操作,又称折叠操作。该操作通过重复执行某个合并操作,将一系列的输入元素汇总成单个的结果。例如,寻找一组数字的总和或最大值,或将元素累加到一个列表中。Stream类有两个通用的缩减操作——reduce()和collect();专用的缩减操作也有不少,例如sum()、max()或count()。
当然,上述的种种操作也可以用简单的串行循环来完成,如下所示:
int sum = 0;
for (int x : numbers) {
sum += x;
}
然而,比起这样的可变累加操作,使用缩减操作的理由更加充分。首先,缩减操作“更抽象”。在执行操作时,它将元素集合视作“流”这个整体,而非单个元素。更重要的是,只要用于处理元素的函数满足结合律(associative)并且是无状态的(stateless),合理构造的缩减操作本身就是并行化的。例如,想要求一个数字流的和,我们可以这样写:
int sum = numbers.stream().reduce(0, (x,y) -> x+y);
也可以这样写:
int sum = numbers.stream().reduce(0, Integer::sum);
想要安全地并行执行这个缩减操作,几乎无需任何改动:
int sum = numbers.parallelStream().reduce(0, Integer::sum);
如上所示,缩减操作适合并行化,因为它可以并行操作数据的子集,然后将中间结果相结合以获取最终的结果。并行化reduce()操作没有负担,并且库可以提供高效的并行实现,无需额外的同步。(相比之下,可变累加方法不适合并行化。即使编程语言支持“并行的for-each操作”,共享的累加变量sum仍然需要开发者提供线程安全的更新。所需的同步可能会抵消并行带来的性能提升。)
前面的几个“widgets”示例展示了如何将缩减操作与其他操作相结合,用批量操作替换for循环。假设widgets是Widget 对象组成的集合,且widgets有一个getWeight 方法,我们可以通过下面的方法找到权重最高的widget:
OptionalInt heaviest = widgets.parallelStream()
.mapToInt(Widget::getWeight)
.max();
缩减操作有一种更为常见的三参数形式——对泛型类型<T>的元素执行缩减操作,最终产生泛型类型<U>的结果,如下所示:
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
其中,identity元素既是缩减操作的种子值,也是没有元素输入时的默认结果;accumulator函数获取部分结果和下一个元素,由此产生一个新的部分结果;combiner函数将两个部分结果相结合,由此产生一个新的部分结果。(在并行的缩减操作中,combiner函数是必需的。该操作会将输入分区,并计算每个分区的部分累积,最后将部分累积相结合以获得最终的结果。)
更正式的说法是,identity值一定是combiner函数的标识。这意味着,对于任意的u,combiner.apply(identity, u)一定等于u。此外,combiner函数必须满足结合律(associative)并兼容accumulator函数:对于任意的u和t,combiner.apply(u,accumulator.apply(identity, t))调用equals()方法的结果,一定与accumulator.apply(u, t)相等。
作为二参数形式的扩展形式,三参数形式将映射步骤合并到累加步骤中。我们也可以使用三参数形式来计算widgets的权重之和。如下所示:
int sumOfWeights = widgets.stream()
.reduce(0,
(sum, b) -> sum + b.getWeight(),
Integer::sum);
尽管显式的map-reduce具有更强的可读性并更加常用,但是三参数形式可以将映射和缩减融入同一个函数,从而优化重要的工作。
可变缩减
在处理流元素时,可变缩减操作将输入的元素累积到一个可变结果容器中(例如Collection或StringBuilder)。
例如,给定一个字符串流,要将其中的字符串连接成一个长的字符串,我们可以使用普通的缩减操作:
String concatenated = strings.reduce(“”, String::concat)
在这种情况下,不但可以获得想要的结果,甚至还可以并行执行该操作。然而,我们可能对该操作的性能并不满意!因为它会复制大量的字符串,其运行时间是O(n^2),n是字符数。还有一种性能更好的办法,即使用可变缩减操作,将结果累积到StringBuilder中(StringBuilder是用于累积字符串的可变容器)。并行化可变缩减的方法与普通缩减的方法相同。
可变缩减操作被称为 collect(),因为它会将所需的结果收集到结果容器中,例如Collection容器。一个collect 操作需要传入三个函数:supplier函数,即供应者函数,用于构造结果容器的新实例;accumulator函数,即累加器函数,将输入元素合并到结果容器中;以及combiner函数,即组合器函数,将一个结果容器的内容合并到另一个容器中。可变缩减操作的形式与普通缩减的三参数形式十分相似:
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
和reduce()操作一样,用这种抽象形式表示的collect操作也可以直接并行化:只要accumulator和combiner函数满足一定的要求,我们就可以并行累加部分结果,然后将它们组合。例如,要想把字符串形式的流元素收集到ArrayList中,可以使用串行的for-each方法:
ArrayList<String> strings = new ArrayList<>();
for (T element : stream) {
strings.add(element.toString());
}
或者,可以使用并行化的collect方法:
ArrayList<String> strings = stream.collect(() -> new ArrayList<>(),
(c, e) -> c.add(e.toString()),
(c1, c2) -> c1.addAll(c2));
或者,我们可以用一种更简洁的形式来表达,将map操作从accumulator函数中提取出来:
List<String> strings = stream.map(Object::toString)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
此处的supplier函数是一个ArrayList构造方法(ArrayList constructor);accumulator函数将字符串化的元素添加到ArrayList中;然后combiner函数使用addAll 方法将字符串从一个容器复制到另一个容器中。
collect的三个函数——supplier, accumulator, and combiner——是紧密结合的。我们也可以使用Collector 接口的抽象形式来获取这三个函数。例如,用一个标准的Collector重写上面的例子,也能将字符串收集到List中:
List<String> strings = stream.map(Object::toString)
.collect(Collectors.toList());
将可变缩减操作打包到Collector中还有一个优点:可组合性。Collectors类提供了许多预定义的收集器工厂,包括将一个收集器转换为另一个收集器的组合器。
假设我们有一个收集器,用于计算员工流的工资总和,如下所示:
Collector<Employee, ?, Integer> summingSalaries
= Collectors.summingInt(Employee::getSalary);
(第二个泛型参数用“?”表示,这说明该参数的类型无关紧要。)如果要创建一个计算各部门工资总和的收集器,我们可以使用groupingBy方法,并在该方法中传入summingSalaries:
Map<Department, Integer> salariesByDept
= employees.stream().collect(Collectors.groupingBy(Employee::getDepartment,
summingSalaries));
与常规的缩减操作一样,只有在满足适当的条件时,collect()操作才能并行化。对于任意一个部分累加结果而言,与空的结果容器相结合,产生的结果一定与自身相等。也就是说,由一系列的累加器和组合器生成的部分累加结果p,一定和combiner.apply(p,supplier.get())的结果相同。
即便将计算拆分,最后的结果也一定与未拆分的结果相等。例如,对于任意的输入元素t1和t2,最后的计算结果r1和r2一定相等:
A a1 = supplier.get();
accumulator.accept(a1, t1);
accumulator.accept(a1, t2);
R r1 = finisher.apply(a1); //未拆分的结果
A a2 = supplier.get();
accumulator.accept(a2, t1);
A a3 = supplier.get();
accumulator.accept(a3, t2);
R r2 = finisher.apply(combiner.apply(a2, a3)); //拆分的结果
一般情况下,使用Object.equals(Object)方法判断结果是否相等。不过,某些情况下会放宽对顺序的要求,因此不同顺序的结果也可以被视为相等。
缩减、并发和排序
我们会用到一些复杂的缩减操作。例如,用collect()方法创建一个Map:
Map<Buyer, List<Transaction>> salesByBuyer
= txns.parallelStream()
.collect(Collectors.groupingBy(Transaction::getBuyer));
这种情况下,并行执行可能会事与愿违,无法提高效率。因为在实现某些Map时,组合步骤(通过键将一个Map合并到另一个Map的步骤)是高耗能的。
假设,这个缩减操作使用的结果容器是一个允许并发修改的集合,例如ConcurrentHashMap。此时,并行调用累加器会将结果同时存储到同一个共享的结果容器中。如此一来,组合器也就没了用武之地,因为无需合并不同的结果容器。我们将这种情况称为并发缩减。
支持并发缩减的收集器(Collector)具有Collector.Characteristics.CONCURRENT特性。不过,并发收集有一个缺点。如果多个线程同时将结果存放到共享的容器中,那么存放结果的顺序是不确定的。因此,只有当流的排序无关紧要时,才可以使用并发缩减。出现以下几种情况时,Stream.collect(Collector) 方法只会执行并发缩减:
- 流是并行的;
- 收集器具有Collector.Characteristics.CONCURRENT特性;
- 流是无序的,或者收集器具有Collector.Characteristics.UNORDERED特性。
你可以使用BaseStream.unordered()方法确保流是无序的。例如:
Map<Buyer, List<Transaction>> salesByBuyer
= txns.parallelStream()
.unordered()
.collect(groupingByConcurrent(Transaction::getBuyer));
(此处,Collectors.groupingByConcurrent(java.util.function.Function<? super T, ? extends K>) 相当于并发的groupingBy)。
注意,并发插入会牺牲排序。如果我们希望按照流源的顺序排列某个键的元素,那就不能使用并发缩减。这种情况下,只能使用串行缩减,或者基于合并的并行缩减。
结合律
如果以下情况成立,那么作为运算符或函数的op符合结合律:
(a op b) op c == a op (b op c)
扩展到四个项时,就能看出结合律对于并行计算的重要性了:
a op b op c op d == (a op b) op (c op d)
此时,我们可以并行计算(a op b)和(c op d),然后基于各自的结果调用op。
符合结合律的操作包括数值加法、最小值和最大值的计算,以及字符串的连接。
底层流的构建
到目前为止,相关示例通过Collection.stream()或Arrays.stream(Object[])等方法获取一个流。那么,这些流承载的方法是如何实现的呢?
StreamSupport类提供了许多创建流的底层方法,这些方法都用到了某种形式的分割迭代器(Spliterator)。分割迭代器与并行的Iterator类似,描述了一个(可能是无限的)元素集合,支持串行推进、批量遍历,以及将部分输入拆分到另一个并行执行的分割迭代器中。在最底层,所有的流都由一个分割迭代器驱动。
实现分割迭代器的方法有很多,几乎都要在实现的简洁性和流的运行性能之间作出权衡。最简洁但性能最差的方法是Spliterators.spliteratorUnknownSize(java.util.Iterator, int)——基于迭代器创建分割迭代器。虽然该方法创建的分割迭代器可以运行,但是并行的性能很差,因为缺失了规模相关的信息(即底层数据集的大小),并且受限于简单的拆分算法。
相比之下,一个性能好的分割迭代器会提供:均衡且规模明确的拆分;准确的规模信息;分割迭代器或数据的其他很多特性(characteristics),用于优化实现。
可变数据源的分割迭代器面临一个额外的挑战——绑定数据的时间。因为,从创建分割迭代器到执行流管道的这段时间内,数据可能会发生变化。理想情况下,流的分割迭代器具有IMMUTABLE特性或CONCURRENT特性;否则,分割迭代器就是后期绑定的(late-binding)。如果数据源无法直接提供分割迭代器,有可能会通过Supplier间接提供一个分割迭代器,并使用接受Supplier的stream()方法来构建流。注意,只有当流管道的终端操作开始执行后,才能从Supplier获得分割迭代器。
从流源的改动到流管道的执行,存在着潜在的干扰,而上述要求明显降低了潜在干扰的范围。不论流是基于分割迭代器建立的,还是基于Supplier工厂形式建立的,在终端操作开始前修改数据源都不会对流产生影响(前提是,流操作的行为参数是无干扰且无状态的)。更多相关信息,请参考无干扰文档(Non-Interference)。
始于以下版本:
1.8
接口摘要 | |
接口 | 描述 |
BaseStream<T,S extends BaseStream<T,S>> | 作为流的基础接口,BaseStream是支持串行和并行聚合操作的元素序列。 |
Collector<T,A,R> | 提供可变缩减操作(mutable reduction operation)。将输入元素累积到一个可变结果容器中;处理完全部的输入元素后,可以选择将累积结果转换为最终结果。 |
一个元素值是原始类型double的序列,支持串行和并行聚合操作。 | |
DoubleStream的可变构造器。 | |
一个元素值是原始类型int的序列,支持串行和并行聚合操作。 | |
IntStream的可变构造器。 | |
一个元素值是原始类型long的序列,支持串行和并行聚合操作。 | |
LongStream的可变构造器。 | |
Stream<T> | 一个元素序列,支持串行和并行聚合操作。 |
Stream的可变构造器。 |
类摘要 | |
类 | 描述 |
Collector的实现类,用于执行各种缩减操作,例如将元素累积到集合中、根据不同的标准汇总元素等。 | |
底层的实用方法,用于创建和操作流。 |
枚举摘要 | |
枚举 | 描述 |
收集器属性的特征,用于优化缩减操作的实现。 |
模块 java.base
Interface BaseStream<T,S extends BaseStream<T,S>>
类型参数:
T - 流元素的类型
S - 执行BaseStream接口的流类型
所有超接口:
所有已知的子接口:
DoubleStream、 IntStream、 LongStream、Stream<T>
public interface BaseStream<T,S extends BaseStream<T,S>>
extends AutoCloseable
作为流的基础接口,BaseStream是支持串行和并行聚合操作的元素序列。
下面的聚合操作实例使用了Stream 和IntStream这两个类型的流,用于计算red widgets的权重之和:
int sum = widgets.stream()
.filter(w -> w.getColor() == RED)
.mapToInt(w -> w.getWeight())
.sum();
想要了解有关流、流操作、流管道和并行性的更多说明,请参阅 Stream文档和java.util.stream文档。这些文档定义了所有流类型的行为。
始自以下版本:
1.8
另请参考:
Stream、IntStream、LongStream、DoubleStream、java.util.stream
方法摘要
所有方法 实例方法 抽象方法 | ||
修饰符和类型 | 方法 | 描述 |
void | close() | 调用全部的关闭处理程序,用于关闭此流。 |
boolean | 在执行终端操作前,调用该方法判断此流是否并行执行。 | |
iterator() | 返回此流元素的迭代器。 | |
返回一个新流,该流等同于添加了指定的关闭处理程序的调用流。 | ||
parallel() | 返回一个新流,该流等同于并行的调用流。 | |
返回一个新流,该流等同于串行的调用流。 | ||
返回此流元素的分割迭代器。 | ||
返回一个新流,该流等同于无序的(unordered)调用流。 |
方法详情
iterator
返回此流元素的迭代器。
这是一个终端操作(terminal operation)。
返回:
此流的元素迭代器
spliterator
Spliterator<T> spliterator()
返回此流元素的分割迭代器。
这是一个终端操作(terminal operation)。
返回的分割迭代器描述了流管道的特征集合(即流源的分割迭代器和中间操作所具有的特征)。某些情况下,分割迭代器的实现可能会描述特征集合的子集。例如,计算部分或所有流管道的整体特征集合的耗能过高,因此只能描述子集。
返回:
此流元素的分割迭代器
isParallel
boolean isParallel()
在执行终端操作前,调用该方法判断此流是否并行执行。注意,在执行终端操作后,调用此方法可能会产生不可预知的结果。
返回:
如果此流是并行执行,就返回true。
sequential
S sequential()
返回一个新流,该流等同于串行的调用流。如果调用流本身就是串行流,或者底层流已被修改为串行流,新流就等同于调用流自身。
这是一个中间操作(intermediate operation)。
返回:
串行流
parallel
S parallel()
返回一个新流,该流等同于并行的调用流。如果调用流本身就是并行流,或者底层流已被修改为并行流,新流就等同于调用流自身。
这是一个中间操作(intermediate operation)。
返回:
并行流
unordered
S unordered()
返回一个新流,该流等同于无序的(unordered)调用流。如果调用流本身就是无序流,或底层流已被修改为无序流,新流就等同于调用流自身。
这是一个中间操作(intermediate operation)。
返回:
无序流
onClose
S onClose(Runnable closeHandler)
返回一个新流,该流等同于添加了指定的关闭处理程序的调用流。
当我们使用close()方法关闭这个新流时,关闭处理程序会按照添加时的顺序执行。即使前面的关闭处理程序抛出了异常,所有的处理程序仍将运行。如果多个关闭处理程序都抛出了异常,那么只有第一个异常会被传递给close()的调用者;其余的异常将被抑制[^1],并附加到第一个异常上。(只有当其他异常和第一个异常相同时才不会被屏蔽,因为异常无法屏蔽自身。)
某些情况下,可能会返回调用流自身。
这是一个中间操作(intermediate operation)。
参数:
closeHandler - 流关闭时执行的任务
返回:
一个有关闭处理程序的流,该程序将在流关闭时运行
close
void close()
调用全部的关闭处理程序,用于关闭此流。
源自:
AutoCloseable接口的close方法
另请参照:
模块 java.base
Interface DoubleStream
所有超接口:
AutoCloseable、 BaseStream<Double, DoubleStream>
public interface DoubleStream
extends BaseStream<Double, DoubleStream>
一个元素值是原始类型double的序列,支持串行和并行聚合操作。这是流(Stream)的double原始类型特化。
下面的聚合操作实例使用了Stream和DoubleStream这两个类型的流,用于计算red widgets的权重之和:
double sum = widgets.stream()
.filter(w -> w.getColor() == RED)
.mapToDouble(w -> w.getWeight())
.sum();
想要了解有关流、流操作、流管道和并行性的更多说明,请参考Stream文档和java.util.stream文档。
始于以下版本:
1.8
另请参考:
嵌套类摘要
嵌套类 | ||
修饰符和类型 | 接口 | 描述 |
static interface | DoubleStream的可变构造器。 |
方法摘要
所有方法 静态方法 实例方法 抽象方法 默认方法 | ||
修饰符和类型 | 方法 | 描述 |
boolean | allMatch(DoublePredicate predicate) | 检验调用流的全部元素是否都与给定的谓词匹配。如果匹配,就返回true;否则返回false。 |
boolean | anyMatch(DoublePredicate predicate) | 检验调用流中是否存在任意元素与给定的谓词匹配。如果匹配,就返回true;否则返回false。 |
average() | 返回一个OptionalDouble,描述了调用流元素的算数平均值。如果调用流为空,则返回空的optional。 | |
boxed() | 返回一个流,包含了装箱为Double类型的调用流元素。 | |
static DoubleStream.Builder | builder() | 返回一个DoubleStream的构造器。 |
<R> R | collect(Supplier<R> supplier, ObjDoubleConsumer<R> accumulator, BiConsumer<R,R> combiner) | 对调用流的元素执行可变缩减操作(mutable reduction)。 |
static DoubleStream | concat(DoubleStream a, DoubleStream b) | 创建一个延迟连接的流,流元素包括第一个流的所有元素,后接第二个流的所有元素。 |
long | count() | 返回调用流元素的数量。 |
distinct() | 返回一个流,包含了去重的调用流元素。 | |
default DoubleStream | dropWhile(DoublePredicate predicate) | 如果调用流是有序的, 先删除与给定的谓词相匹配的元素最长前缀,之后返回由剩余元素组成的流。 |
static DoubleStream | empty() | 返回一个空的、串行的DoubleStream。 |
filter(DoublePredicate predicate) | 返回一个流,包含了与给定的谓词相匹配的调用流元素。 | |
findAny() | 返回一个OptionalDouble,描述了调用流的部分元素。如果调用流为空,则返回空的OptionalDouble。 | |
返回一个OptionalDouble,描述了调用流的第一个元素。如果调用流为空,则返回空的OptionalDouble。 | ||
flatMap(DoubleFunction<? extends DoubleStream> mapper) | 先将给定的映射函数应用于调用流的每个元素,生成一个映射流。然后,用映射流替换调用流的元素。最后,返回一个流,该流由替换后的结果组成。 | |
void | forEach(DoubleConsumer action) | 对调用流的每个元素执行操作(action)。 |
void | forEachOrdered(DoubleConsumer action) | 对调用流的每个元素执行操作(action)。如果调用流定义了相遇顺序,就一定会按照相遇顺序执行操作。 |
static DoubleStream | 返回一个无限、串行的无序流,流元素由给定的DoubleSupplier生成。 | |
static DoubleStream | iterate(double seed, DoublePredicate hasNext, DoubleUnaryOperator next) | 在满足给定的谓词hasNext的前提下,将给定的next函数迭代应用到初始元素seed,产生并返回一个有序、串行的DoubleStream。 |
static DoubleStream | iterate(double seed, DoubleUnaryOperator f) | 将函数f迭代应用到初始元素seed,然后返回一个无限、串行、有序的DoubleStream。返回流由seed、f(seed) 、f(f(seed))等组成。 |
limit(long maxSize) | 返回一个流,包含了从调用流中截取的元素。截取长度不超过maxSize。 | |
map(DoubleUnaryOperator mapper) | 将给定的函数应用于调用流的元素,然后返回一个由映射结果组成的流。 | |
mapToInt(DoubleToIntFunction mapper) | 将给定的函数应用于调用流的元素,然后返回一个由映射结果组成的IntStream。 | |
mapToLong(DoubleToLongFunction mapper) | 将给定的函数应用于调用流的元素,然后返回一个由映射结果组成的LongStream。 | |
<U> Stream<U> | mapToObj(DoubleFunction<? extends U> mapper) | 将给定的函数应用于调用流的元素,然后返回一个由映射结果组成的对象流Stream。 |
max() | 返回一个OptionalDouble,描述了调用流的最大元素。如果调用流为空,则返回一个空的OptionalDouble。 | |
min() | 返回一个OptionalDouble,描述了调用流的最小元素。如果调用流为空,则返回一个空的 OptionalDouble。 | |
boolean | noneMatch(DoublePredicate predicate) | 检验调用流中是否不存在与给定的谓词相匹配的元素。如果不存在,就返回true。 |
static DoubleStream | of(double t) | 返回一个串行的DoubleStream,由指定的单个元素组成。 |
static DoubleStream | of(double... values) | 返回一个串行的有序流,由指定的多个元素组成。 |
peek(DoubleConsumer action) | 返回一个流,包含了调用流的元素。从结果流中消耗元素时,将对每个元素执行给定的操作(action)。 | |
double | reduce(double identity, DoubleBinaryOperator op) | 基于给定的identity值和满足结合律的累积函数,对调用流的元素执行缩减操作,之后返回一个缩减值。 (有关结合律以及缩减操作的更多内容,请参考associative 或reduction文档。) |
基于满足结合律的累积函数,对调用流的元素执行缩减操作。如果存在缩减值,就返回一个OptionalDouble类的缩减值。 (有关结合律以及缩减操作的更多内容,请参考associative 或reduction文档。) | ||
skip(long n) | 摒弃调用流的前n 个元素,然后返回由剩余元素组成的流。 | |
sorted() | 按照自然排序的顺序,返回一个由调用流元素组成的流。 | |
double | sum() | 返回调用流元素的总和。 |
返回一个DoubleSummaryStatistics,描述了调用流元素的各类摘要数据。 | ||
default DoubleStream | takeWhile(DoublePredicate predicate) | 如果调用流是有序的,提取出与给定的谓词相匹配的元素最长前缀,之后返回由最长前缀组成的流。 |
double[] | toArray() | 返回一个由调用流元素组成的数组。 |
在interface java.util.stream.BaseStream 中声明的方法
close、isParallel、iterator、onClose、parallel、sequential、spliterator、unordered
方法详情
filter
DoubleStream filter(DoublePredicate predicate)
返回一个流,包含了与给定的谓词相匹配的调用流元素。
这是一个中间操作(intermediate operation)。
参数:
predicate - 一个无干扰、无状态的谓词,应用于每个元素,以判断它是否应该被包含在新流中。
(有关无干扰或无状态的更多内容,请参考non-interfering或stateless文档。)
返回:
新流
map
DoubleStream map(DoubleUnaryOperator mapper)
将给定的函数应用于调用流的元素,然后返回一个由映射结果组成的流。
这是一个中间操作(intermediate operation)。
参数:
mapper - 一个无干扰、无状态的函数,该函数应用于每个元素。
(有关无干扰或无状态的更多内容,请参考non-interfering或stateless文档。)
返回:
新流
mapToObj
<U> Stream<U> mapToObj(DoubleFunction<? extends U> mapper)
将给定的函数应用于调用流的元素,然后返回一个由映射结果组成的对象流Stream。
这是一个中间操作(intermediate operation)。
类型形参:
U - 新流的元素类型
参数:
mapper - 一个无干扰、无状态的函数,该函数应用于每个元素。
(有关无干扰或无状态的更多内容,请参考non-interfering或stateless文档。)
返回:
新流
mapToInt
IntStream mapToInt(DoubleToIntFunction mapper)
将给定的函数应用于调用流的元素,然后返回一个由映射结果组成的IntStream。
这是一个中间操作(intermediate operation)。
参数:
mapper - 一个无干扰、无状态的函数,该函数应用于每个元素。
(有关无干扰或无状态的更多内容,请参考non-interfering或stateless文档。)
返回:
新流
mapToLong
LongStream mapToLong(DoubleToLongFunction mapper)
将给定的函数应用于调用流的元素,然后返回一个由映射结果组成的LongStream。
这是一个中间操作(intermediate operation)。
参数:
mapper - 一个无干扰、无状态的函数,该函数应用于每个元素。
(有关无干扰或无状态的更多内容,请参考non-interfering或stateless文档。)
返回:
新流
flatMap
DoubleStream flatMap(DoubleFunction<? extends DoubleStream> mapper)
先将给定的映射函数应用于调用流的每个元素,生成一个映射流。然后,用映射流替换调用流的元素。最后,返回一个流,该流由替换后的结果组成。替换完成后,映射流关闭(closed)。(如果映射流是null,说明调用流是空流。)
这是一个中间操作(intermediate operation)。
参数:
mapper - 一个无干扰、无状态的函数,该函数应用于每个元素,用于创建一个由新值组成的DoubleStream。
(有关无干扰或无状态的更多内容,请参考non-interfering或stateless文档。)
返回:
新流
另请参考:
distinct
DoubleStream distinct()
返回一个流,包含了去重的调用流元素。在比较元素是否相等时,使用的是Double.compare(double, double)方法。
这是一个有状态的中间操作(stateful intermediate operation)。
返回:
结果流
sorted
DoubleStream sorted()
按照自然排序的顺序,返回一个由调用流元素组成的流。在比较元素是否相等时,使用的是Double.compare(double, double)方法。
这是一个有状态的中间操作(stateful intermediate operation)。
返回:
结果流
peek
DoubleStream peek(DoubleConsumer action)
返回一个流,包含了调用流的元素。从结果流中消耗元素时,将对每个元素执行给定的操作(action)。
这是一个中间操作(intermediate operation)。
在并行的流管道中,无论元素是通过上游操作在哪个时间或哪个线程中被消耗,给定的操作都可能被调用。如果操作修改了共享的状态,它需要负责提供所需的同步。
API 注释:
此方法主要用于调试。在调试过程中,当你想在元素流经管道的某个点时进行查看,可以使用此方法:
DoubleStream.of(1, 2, 3, 4)
.filter(e -> e > 2)
.peek(e -> System.out.println(“Filtered value: ” + e))
.map(e -> e * e)
.peek(e -> System.out.println(“Mapped value: ” + e))
.sum();
在某些情况下,流实现可以优化部分或全部元素的生成(例如,findFirst之类的短路操作,或者count()方法中展示的例子)。因此,操作(action)无法应用于这些元素。
参数:
action - 从流中消耗元素时,执行无干扰(non-interfering)的操作。
返回:
新流
limit
DoubleStream limit(long maxSize)
返回一个流,包含了从调用流中截取的元素。截取长度不超过maxSize。
这是一个短路、有状态的中间操作(short-circuiting stateful intermediate operation)。
API 注释:
通常情况下,limit()在串行的流管道中是一种低消耗的操作。但是在有序的并行管道中,该操作消耗极高,尤其是当maxSize的值较大时。因为limit(n)并不是要返回任意的n个元素,而是要按照相遇顺序返回前n个元素。在语义允许的情况下,使用无序流源(例如generate(DoubleSupplier),或使用 BaseStream.unordered() 删除排序约束,都可能会导致并行管道中的limit()明显加速。在要求与相遇顺序保持一致的情况下,如果并行管道中的limit()遇到了性能不高或内存利用率不佳的问题,那么使用BaseStream.sequential()切换到串行执行也许可以提高性能。
参数:
maxSize - 从调用流中截取的元素数量
返回:
新流
抛出:
IllegalArgumentException - 如果maxSize为负数
skip
DoubleStream skip(long n)
摒弃调用流的前n个元素,然后返回由剩余元素组成的流。如果调用流的元素数小于 n,返回一个空流。
这是一个有状态的中间操作(stateful intermediate operation)。
API 注释:
通常情况下,skip()在串行的流管道中是一种低消耗的操作。但是在有序的并行管道中,该操作消耗极高,尤其是当n值较大时。因为skip(n)不是要摒弃任意n个元素,而是要按照相遇顺序摒弃前n个元素。在语义允许的情况下,使用无序流源(例如generate(DoubleSupplier)),或使用BaseStream.unordered()删除排序约束,都可能会导致并行管道中的skip()明显加速。在要求与相遇顺序保持一致的情况下,如果并行管道中的skip()遇到了性能不高或内存利用率不佳的问题,那么使用BaseStream.sequential()切换到串行执行也许可以提高性能。
参数:
n - 摒弃的元素数量
返回:
新流
抛出:
IllegalArgumentException - 如果n为负值
takeWhile
default DoubleStream takeWhile(DoublePredicate predicate)
如果调用流是有序的,提取出与给定的谓词相匹配的元素最长前缀,之后返回由最长前缀组成的流。如果调用流是无序的,提取出与给定的谓词相匹配的元素子集,之后返回由子集组成的流。
如果调用流是有序的,最长前缀就是一个与谓词相匹配的连续元素序列。序列的第一个元素就是调用流的第一个元素,而紧跟在序列最后一个元素之后的元素与谓词不匹配。
如果调用流是无序的,并且部分(但并非全部)流元素与谓词匹配,这个操作的行为就是非确定性的。因为它可以自由地获取匹配元素的任意子集(包括空集在内)。
如果调用流的所有元素都与谓词相匹配,那么无论调用流是否有序,此操作都将获取所有元素(即结果与输入相同);反之,如果调用流中没有元素与谓词匹配,则不获取任何元素(即结果是空流)。
这是一个短路、有状态的中间操作(short-circuiting stateful intermediate operation)。
API 注释:
虽然takeWhile()在串行流的管道中通常是低消耗的,但在有序的并行管道中消耗很高。因为该操作不是要返回任意的有效前缀,而是要按照相遇顺序返回元素的最长前缀。在语义允许的情况下,使用无序流源(例如generate(DoubleSupplier)),或使用BaseStream.unordered()删除排序约束,都可能会导致并行管道中的takeWhile()明显加速。在要求与相遇顺序保持一致的情况下,如果并行管道中的takeWhile()遇到了性能不高或内存利用率不佳的问题,那么使用 BaseStream.sequential()切换到串行执行也许可以提高性能。
实现要求:
默认的实现会获取调用流的分割迭代器(spliterator),并对其进行包装,以便支持该操作在遍历中的语义;然后,返回一个新流,该流与被包装的分割迭代器相关联。返回的流保留了调用流的执行特性(即根据BaseStream.isParallel()的结果确定串行或并行),但被包装的分割迭代器也许不支持拆分。当返回流关闭时,返回流和调用流的关闭处理程序都将被调用。
参数:
predicate - 一个无干扰、无状态的谓词,该谓词应用于每个元素,用于确定元素的最长前缀。
(有关无干扰或无状态的更多内容,请参考non-interfering或stateless文档。)
返回:
新流
始于以下版本:
9
dropWhile
default DoubleStream dropWhile(DoublePredicate predicate)
如果调用流是有序的, 先删除与给定的谓词相匹配的元素最长前缀,之后返回由剩余元素组成的流。反之,如果调用流是无序的,先删除与给定的谓词相匹配的元素子集,之后返回由剩余元素组成的流。
如果调用流是有序的,最长前缀就是一个与谓词相匹配的连续元素序列。序列的第一个元素是调用流的第一个元素,而紧跟在序列最后一个元素之后的元素与谓词不匹配。
如果调用流是无序的,并且部分(但并非全部)流元素与谓词匹配,这个操作的行为就是非确定性的。因为它可以自由地删除匹配元素的任意子集(包括空集在内)。
如果调用流的所有元素都与谓词相匹配,那么无论调用流是否有序,此操作都将删除所有元素(即结果是空流);反之,如果调用流中没有元素与谓词匹配,就会不删除任何元素(即结果与输入相同)。
这是一个有状态的中间操作(stateful intermediate operation)。
API 注释:
虽然dropWhile()在串行流的管道中通常是低消耗的,但在有序的并行管道中消耗很高。因为该操作并不是要返回任意的有效前缀,而是要按照相遇顺序返回元素的最长前缀。在语义允许的情况下,使用无序流源(例如generate(DoubleSupplier)),或使用BaseStream.unordered()删除排序约束,都可能会导致并行管道中的dropWhile()明显加速。在要求与相遇顺序保持一致的情况下,如果并行管道中的dropWhile()遇到了性能不高或内存利用率不佳的问题,那么使用BaseStream.sequential ()切换为串行执行也许可以提高性能。
实现要求:
默认的实现会获取调用流的分割迭代器(spliterator),并对其进行包装,以便支持该操作在遍历中的语义;然后,返回一个新流,该流与被包装的分割迭代器相关联。返回的流保留了调用流的执行特性(即根据BaseStream.isParallel()的结果确定串行或并行),但被包装的分割迭代器也许不支持拆分。当返回流关闭时,返回流和调用流的关闭处理程序都将被调用。
参数:
predicate - 一个无干扰、无状态的谓词,该谓词应用于元素,用于确定元素的最长前缀。
(有关无干扰或无状态的更多内容,请参考non-interfering或stateless文档。)
返回:
新流
始于以下版本:
9
forEach
void forEach(DoubleConsumer action)
对调用流的每个元素执行操作(action)。
这是一个终端操作(terminal operation)。
对并行的流管道来说,该方法不能保证流的相遇顺序,因为这样会牺牲并行性带来的好处。对于任意一个给定的元素,无论库选择的什么时间或什么线程,操作(action)都将执行。如果操作访问的是共享状态,它要负责提供所需的同步。
参数:
action - 对元素执行无干扰的(non-interfering)操作。
forEachOrdered
void forEachOrdered(DoubleConsumer action)
对调用流的每个元素执行操作(action)。如果调用流定义了相遇顺序,就一定会按照相遇顺序执行操作。
这是一个终端操作(terminal operation)。
参数:
action - 对元素执行无干扰的(non-interfering)操作。
另请参考:
toArray
double[] toArray()
返回一个由调用流元素组成的数组。
这是一个终端操作(terminal operation)。
返回:
由调用流元素组成的数组
reduce
double reduce(double identity, DoubleBinaryOperator op)
基于给定的identity值和满足结合律的累积函数,对调用流的元素执行缩减操作,之后返回一个缩减值。
(有关结合律以及缩减操作的更多内容,请参考associative或reduction文档。)
该操作等同于:
double result = identity;
for (double element : this stream)
result = accumulator.applyAsDouble(result, element)
return result;
但不限于串行执行。
identity 值必须是accumulator函数的标识值。这意味着,对于所有x, accumulator.apply(identity, x)都等于x。accumulator 函数必须是满足结合律(associative)的函数。
这是一个终端操作(terminal operation)。
API 注释:
Sum、min、max和average都是缩减的特殊情况。数字流的求和可以表示为:
double sum = numbers.reduce(0, (a, b) -> a+b);
或更简洁地表示为:
double sum = numbers.reduce(0, Double::sum);
相比起改变循环的运行总数,这种执行聚合操作的方式似乎更迂回。然而,缩减操作的并行化更优雅,无需额外的同步,并且大大降低了数据竞争的风险。
参数:
identity - 累积函数的标识值
op - 一个满足结合律、无干扰、无状态的函数,用于合并两个值。
(有关结合律、无干扰或无状态的更多内容,请参考associative、non-interfering或stateless文档。)
返回:
缩减的结果
另请参考:
sum()、 min()、 max()、 average()
reduce
OptionalDouble reduce(DoubleBinaryOperator op)
基于满足结合律的累积函数,对调用流的元素执行缩减操作。如果存在缩减值,就返回一个OptionalDouble类的缩减值。(有关结合律以及缩减操作的更多内容,请参考associative
或reduction文档。)
该操作等同于:
boolean foundAny = false;
double result = null;
for (double element : this stream) {
if (!foundAny) {
foundAny = true;
result = element;
}
else
result = accumulator.applyAsDouble(result, element);
}
return foundAny ? OptionalDouble.of(result) : OptionalDouble.empty();
但不限于串行执行。
注意,accumulator 函数必须是一个满足结合律的(associative )函数。
这是一个终端操作(terminal operation)。
参数:
op - 一个满足结合律、无干扰、无状态的函数,用于合并两个值。
(有关结合律、无干扰或无状态的更多内容,请参考associative、non-interfering或stateless文档。)
返回:
缩减的结果
另请参考:
reduce(double, DoubleBinaryOperator)
collect
<R> R collect( Supplier <R> supplier, ObjDoubleConsumer <R> accumulator, BiConsumer <R, R > combiner)
对调用流元素执行可变缩减操作(mutable reduction)。可变缩减的缩减值是一个可变结果容器(例如ArrayList),并且元素的合并是通过更新结果的状态来完成的,而非替换结果。由此产生的结果等同于:
R result = supplier.get();
for (double element : this stream)
accumulator.accept(result, element);
return result;
和reduce(double, DoubleBinaryOperator)一样,并行化collect操作无需额外同步。
这是一个终端操作(terminal operation)。
类型参数:
R - 可变结果容器的类型
参数:
supplier - 用于创建新的可变结果容器的函数。并行执行时,此函数可能会被多次调用,并且每次都必须返回一个新值。
accumulator - 一个满足结合律、无干扰、无状态的函数,该函数会将一个元素加入一个结果容器中。
combiner - 一个满足结合律、无干扰、无状态的函数,该函数接受两个部分结果容器并将二者合并,因此必须和accumulator函数兼容。combiner函数会将第二个结果容器的元素加入第一个结果容器中。
(有关结合律、无干扰或无状态的更多内容,请参考associative、non-interfering或stateless文档。)
返回:
缩减的结果
另请参考:
Stream.collect(Supplier, BiConsumer, BiConsumer)
sum
double sum()
返回调用流元素的总和。求和是缩减(reduction)的一种特殊情况。如果浮点求和是精确的,则此方法等同于:
return reduce(0, Double::sum);
但是,由于浮点求和并不精确,上述代码不一定等同于sum()方法。
浮点和的值是既是输入值的函数,也是加法运算顺序的函数。考虑到实现的灵活性,不规定该方法的加法运算顺序,以提高计算结果的速度和准确性。特别要注意的是,与double 值的简单求和相比,该方法可能会使用补偿求和或其他技巧,以减少数值和的误差范围。因为该方法没有指定运算顺序,而且可能会采用不同的求和方案,所以,相同的输入元素也可能会产生不同的。
许多情况都会导致一个非有限的和。即使所有的元素都是有限的,求和时也可能会出现这种情况。如果任意元素是非有限的,那么总和也是非有限的:
- 如果任意元素是NaN,最终的总和也是NaN。
- 如果元素中存在一个或多个无穷大,总和是无穷大或NaN。
- 如果元素中存在相反符号的无穷大,总和是NaN。
- 如果元素中存在某个符号的无穷大,中间和却溢出到相反符号的无穷大,那么,总和可能是 NaN。
有限值的中间和可能会溢出到相反符号的无穷大;如果发生了这种情况,即使元素都是有限的,最终的总和也是NaN。如果所有的元素都是零,无法确保最终的总和会保留零的符号。
这是一个终端操作(terminal operation)。
API 注释:
通过增加绝对量,元素的分类往往会产生更准确的结果。
返回:
调用流元素的总和
min
OptionalDouble min()
返回一个OptionalDouble,描述了调用流的最小元素。如果调用流为空,则返回一个空的 OptionalDouble。如果流中存在任意元素为NaN,那么最小元素是Double.NaN。与数值比较运算符不同,该方法认为负零绝对小于正零。这是缩减(reduction)的一种特殊情况,等同于:
return reduce(Double::min);
这是一个终端操作(terminal operation)。
返回:
由调用流中最小元素组成的OptionalDouble;如果调用流为空,则返回空的optional。
max
OptionalDouble max()
返回一个OptionalDouble,描述了调用流的最大元素。如果调用流为空,则返回一个空的 OptionalDouble。如果流中存在任意元素为NaN,那么最大的元素是Double.NaN。与数值比较运算符不同,该方法认为负零绝对小于正零。这是缩减(reduction)的一种特殊情况,等同于:
return reduce(Double::max);
这是一个终端操作(terminal operation)。
返回:
由调用流的最大元素组成的OptionalDouble;如果调用流为空,则返回空的optional。
count
long count()
返回调用流元素的数量。这是缩减(reduction)的一种特殊情况,等同于:
return mapToLong(e -> 1L).sum();
这是一个终端操作(terminal operation)。
API 注释:
如果能够直接从流源中计算元素的数量,流实现可能不会执行流管道(不论是串行还是并行)。在这种情况下,既不会遍历流源的元素,也不会评估任何中间操作。有副作用的行为参数可能会受到影响(注意,除了调试等无害的情况外,强烈建议不要使用有副作用行为参数)。例如,请思考下面这个流:
DoubleStream s = DoubleStream.of(1, 2, 3, 4);
long count = s.peek(System.out::println).count();
流源的元素数量是已知的,并且中间操作peek没有注入或删除流元素(可能是flatMap或filter 操作)。因此,结果是4,不需要执行管道,并且副作用是打印元素。
返回:
调用流的元素数量
average
OptionalDouble average()
返回一个OptionalDouble,描述了调用流元素的算数平均值。如果调用流为空,则返回空的optional。
计算出来的平均值可以在数值上有变动,并且在计算总和时有特殊行为;更多细节请参考sum()。
这个平均值是缩减(reduction)的一种特殊情况。
这是一个终端操作(terminal operation)。
API 注释:
通过增加绝对量, 元素的排序往往会产生更准确的结果。
返回:
一个OptionalDouble,包含了调用流的平均元素;如果调用流为空,则返回空的optional。
summaryStatistics
DoubleSummaryStatistics summaryStatistics()
返回一个DoubleSummaryStatistics,该结果描述了调用流元素的各类摘要数据。这是缩减(reduction)的一种特殊情况。
这是一个终端操作(terminal operation)。
返回:
返回DoubleSummaryStatistics,描述了调用流元素的各类摘要数据。
anyMatch
boolean anyMatch(DoublePredicate predicate)
检验调用流中是否存在任意元素与给定的谓词匹配。如果不是确定结果的必要条件,可以不在所有元素上评估谓词。如果调用流是空流,不评估谓词,直接返回false。
这是一个短路的终端操作(short-circuiting terminal operation)。
API 注释:
此方法基于调用流元素评估谓词的存在量化(对于某些 x ~ P(x))。
参数:
predicate - 一个无干扰、无状态的谓词,该谓词应用于调用流元素。
(有关无干扰或无状态的更多内容,请参考non-interfering或stateless文档。)
返回:
如果调用流中存在任意元素与给定的谓词匹配,返回true ;否则返回false
allMatch
boolean allMatch(DoublePredicate predicate)
检验调用流的全部元素是否都与给定的谓词匹配。如果不是确定结果的必要条件,可以不在所有元素上评估谓词。如果调用流是空流,不评估谓词,直接返回true。
这是一个短路的终端操作(short-circuiting terminal operation)。
API 注释:
此方法基于流元素评估谓词的全称量化(对于所有x P(x))。如果调用流为空,量词都为空满足且始终为真(无论P(x)是什么)。
参数:
predicate - 一个无干扰、无状态的谓词,该谓词应用于调用流元素。
(有关无干扰或无状态的更多内容,请参考non-interfering或stateless文档。)
返回:
如果调用流的所有元素都与给定的谓词匹配,或调用流为空,就返回true ;否则返回false。
noneMatch
boolean noneMatch(DoublePredicate predicate)
检验调用流中是否不存在与给定的谓词相匹配的元素。如果不是确定结果的必要条件,可以不在所有元素上评估谓词。如果调用流为空,不评估谓词,直接返回true。
这是一个短路的终端操作(short-circuiting terminal operation)。
API 注释:
此方法评估谓词对流元素的全称量化(对于所有x ~P(x))。如果调用流为空,量词都为空满足且始终为真(无论P(x)如何)。
参数:
predicate - 一个无干扰、无状态的谓词,该谓词应用于调用流元素。
(有关无干扰或无状态的更多内容,请参考non-interfering或stateless文档。)
返回:
如果调用流中没有元素与给定的谓词匹配,或调用流为空流,就返回true ;否则返回false。
findFirst
OptionalDouble findFirst()
返回一个OptionalDouble,描述了调用流的第一个元素。如果调用流为空,则返回空的OptionalDouble。如果调用流没有相遇顺序,就返回任意一个元素。
这是一个短路的终端操作(short-circuiting terminal operation)。
返回:
描述调用流中第一个元素的OptionalDouble。如果调用流为空,则返回空的OptionalDouble。
findAny
OptionalDouble findAny()
返回一个OptionalDouble,描述了调用流的部分元素。如果调用流为空,则返回空的OptionalDouble。
这是一个短路的终端操作(short-circuiting terminal operation)。
显然,这个操作的行为是非确定性的;它可以在流中自由选择任意的元素。这是为了在并行操作中实现最佳性能;而代价是同一流源的多次调用也许不会返回相同的结果。(如果想要稳定的结果,请改用findFirst()。)
返回:
描述调用流里某些元素的OptionalDouble。如果调用流为空,则返回空的OptionalDouble。
另请参考:
boxed
返回一个流,包含了装箱为Double类型的调用流元素。
这是一个中间操作(intermediate operation)。
返回:
一个流,包含了装箱为Double类型的调用流元素
builder
static DoubleStream.Builder builder()
返回一个DoubleStream的构造器。
返回:
流的构造器
empty
static DoubleStream empty()
返回一个空的、串行的DoubleStream。
返回:
空的串行流
of
static DoubleStream of(double t)
返回一个串行的DoubleStream,由指定的单个元素组成。
参数:
t - 单个元素
返回:
单例串行流
of
static DoubleStream of(double... values)
返回一个串行的有序流,由指定的多个元素组成。
参数:
values - 新流的元素
返回:
新流
iterate
static DoubleStream iterate(double seed, DoubleUnaryOperator f)
将函数f迭代应用到初始元素seed,然后返回一个无限、串行、有序的DoubleStream。返回流由seed、f(seed) 、f(f(seed))等组成。
返回流的第一个元素(即下标为0的元素)就是给定的初始元素seed。当n > 0时,下标为n的元素,就是函数f应用于下标为n-1的元素所产生的结果。
在应用于后续元素之前,函数f应用于某个元素的操作就已经完成了(即符合 happens-before规则)。无论库选择了哪个线程,操作都可以在给定的元素上执行。
参数:
seed - 初始元素
f - 应用于前一个元素的函数,用于生成新元素
返回:
新的、串行的DoubleStream
iterate
static DoubleStream iterate(double seed, DoublePredicate hasNext, DoubleUnaryOperator next)
在满足给定的谓词hasNext的前提下,将给定的next函数迭代应用到初始元素seed,产生并返回一个有序、串行的DoubleStream。当谓词返回false时,流就会终止。
DoubleStream.iterate产生的元素序列与相应的for 循环产生的元素序列相同:
for (double index=seed; hasNext.test(index); index = next.applyAsDouble(index)) {
...
}
如果初始元素上的谓词并不成立,那么结果序列可能是空的。否则,结果序列中的第一个元素是给定的初始元素,下一个元素(如果存在的话)是将next函数应用到初始元素的结果,依此类推,直到谓词给出终止流的指示。
在next 函数应用于某个元素之前,谓词hasNext应用于该元素的操作就已经完成(即符合 happens-before 规则)。接下来,在谓词hasNext应用于后续元素之前,next 函数应用于某个元素的操作就已经完成(即同样符合happens-before规则)。无论库选择了哪个线程,操作都可以在给定的元素上执行。
参数:
seed - 初始元素
hasNext - 应用于元素的谓词,用来确定终止流的时间。
next - 应用于前一个元素的函数,用来生成新元素。
返回:
新的、串行的DoubleStream
始于以下版本:
9
generate
static DoubleStream generate(DoubleSupplier s)
返回一个无限、串行的无序流,流元素由给定的DoubleSupplier生成。该方法适用于创建常数流、随机元素流等。
参数:
s - 用于生成元素的DoubleSupplier
返回:
一个新的DoubleStream,该流无限、无序且串行执行
concat
static DoubleStream concat(DoubleStream a, DoubleStream b)
创建一个延迟连接的流,流元素包括第一个流的所有元素,后接第二个流的所有元素。如果两个输入流都是有序的,结果流也是有序的;如果任意一个输入流是并行的,结果流也是并行的。当结果流关闭时,两个输入流的关闭处理程序都会被调用。
此方法作用于两个输入流,并将它们和各自的流源绑定。因此,对输入流源的后续修改可能不会反映在生成的连接流中。
API 注释:
为了优化执行,此方法将每个流与各自的源绑定,并仅接受两个流作为参数。例如,如果已知每个输入流源的确切大小,可以计算出连接流源的确切大小。如果想要在不绑定或者没有嵌套调用此方法的情况下连接更多的流,需要创建一个由流组成的流,并使用标识函数进行平面映射(flat-mapping)。例如:
DoubleStream concat = Stream.of(s1, s2, s3, s4).flatMapToDouble(s -> s);
实现注释:
使用重复连接构建流时,需小心谨慎。访问深度连接流的元素可能会导致深度调用链,甚至引起StackOverflowError 。
参数:
a - 第一个流
b - 第二个流
返回:
连接两个输入流的流
[^1]: Suppressed exceptions:抑制异常,即通过某种方式忽略了抛出的异常。更多相关信息,请参阅Throwable类文档。