Java 8 Stream API

2025年3月19日 | 阅读 9 分钟

Java 8 引入了用于处理对象集合的功能。流(Stream)不过是一系列对象,它支持各种可以流水线处理以产生所需结果的方法。在进一步深入本主题之前,建议读者掌握 Java 8 的基础知识。

创建流

有多种方法可以从各种资源创建流实例。

空流

要创建空流,必须使用 empty() 方法。

语法

E: 流元素的各种类型。

返回值: 返回一个空的顺序流。

注意:在调用带流参数的方法时,空流有助于避免 NullPointerException。

示例

文件名: EmptyStream.java

输出

Nothing to display

从集合创建

可以从任何类型的集合(Collection、List、Set)创建流。

示例

文件名: StreamCreationEx.java

输出

J2EE
JAVA
Hibernate
Spring
J2EE
JAVA
Hibernate
Spring
JAVA
Hibernate
J2EE
Spring

使用数组

数组也可以是流的来源,或者可以从现有数组或数组的一部分生成数组。

文件名: StreamCreationEx.java

输出

a1
b1
c1
d1
a1
b1
c1
d1
a1
b1
c1
d1

使用 Stream.builder()

当使用构建器时,必须在语句的右侧指定所需的类型,否则,build() 方法会创建 Stream<Object> 实例。

语法

参数

E: 流元素的各种类型。

返回值: 返回一个流构建器。

示例

文件名: StreamCreationEx.java

输出

a1
b1
c1

使用 Stream.generate()

Stream generate(Supplier<T> s) 返回一个无限的顺序无序流,其中每个元素都由提供的 Supplier 生成。它适用于创建常量流、随机元素流等。

语法

其中,stream 是一个接口,E 是流元素的类型。sup 是生成元素的 Supplier,返回值是新的无限顺序无序 Stream。

示例

文件名: StreamCreationEx.java

输出

-412391913
1531711136
-432916310
341021951
-615096017
1339859082

使用 Stream.iterate()

iterate(E, java.util.function.Predicate, java.util.function.UnaryOperator) 方法允许迭代流中的元素直到满足指定条件。该方法返回一个顺序流,该流通过对起始元素进行提供的 next 函数的迭代应用而生成,该函数满足作为参数传递的 hasNext 谓词。只要 hasNext 条件返回 false,流就会终止。

语法

参数:该方法共有三个参数。

st: 起始元素。

hasNext: 用于确定流是否应终止的应用于元素的谓词。

next: 应用于前一个元素以生成新元素的函数。

返回值: 该方法将返回一个新的顺序流。

示例

文件名: StreamCreationEx.java

输出

2.0
1.0
0.5
0.25
0.125

使用原始类型流()

Java 8 提供了从三种原始类型:int、long 和 double 创建流的功能。由于 Stream<E> 是泛型接口,并且无法使用原始类型作为泛型的类型参数,因此创建了三个新的特殊接口:IntStream、LongStream、DoubleStream。

IntStream 示例

文件名: IntStream.java

输出

Sum of the operation of intStreamRangeTest : 3
Time Elapsed of the intStreamRangeTest: 41313298
Sum of the operation of intStreamRangeClosedTest: 10
Time elapsed of the intStreamRangeClosedTest: 562842

LongStream 示例

文件名: LongStream.java

输出

The total count is: 3

DoubleStream 示例

文件名: DoubleStream.java

输出

The total count is: 3

使用 String 流

字符串也可以通过 String 类中的 chars() 方法来创建流。

String 流

文件名: StringStream.java

输出

The total number of character count is: 10

引用流

我们可以实例化流,并保持对其的引用,只要只进行中间操作。在执行终端操作后,流将变得不可访问。下面将对此进行说明。

文件名: IllegalStateExpn.java

输出

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
	at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:647)
	at StreamCreationEx.main(StreamCreationEx.java:14)

说明:我们在主线程中遇到了 IllegalStateException。这是因为 Java 8 中的流永远不能被重用,这种行为也是合乎逻辑的。这是因为流被设计为以函数式风格对源元素应用有限的操作序列,而不是保留元素。如果想避免此异常,应编写以下代码。

文件名: IllegalStateExpnCorrection.java

输出

Optional[a1]

流管道

为了按顺序对数据源的元素执行操作并聚合它们的结果,需要三个部分。


中间操作(S)
终端操作。

中间操作会修改或过滤流中的元素,并返回一个新的流。例如:filter、distinct、map、limit、sorted。

终端操作会产生副作用或结果,标志着流的结束。例如:forEach、reduce、collect、min、count、anyMatch、max、noneMatch、allMatch。

管道会对中间操作和终端操作进行链式处理,以一种表达性和流畅的方式处理数据。每个操作都接收前一个操作的输入并为下一个操作生成输出。管道允许声明式和简洁的编码。结果可以在终端操作中找到。

文件名: PipelineStream.java

输出

The sum of twice of odd numbers in the input list is: 78

惰性求值

惰性求值,也称为按需求值,是一种求值策略,其中求值被推迟,直到其值被需要为止。只有在对流调用终端操作时,才会对流执行所有中间操作。在 Java 流中,惰性求值是允许进行显著优化的主要特性之一。

文件名: LazyEvaluation.java

输出

Result is: 
Filter Done: 5
Filter Done: 9
Filter Done: 15
[5, 9, 15]

说明:众所周知,主方法中的第一条语句首先执行,然后是第二条,之后是第三条,依此类推。按照这个概念,输出应该是

然而,情况并非如此。包含"Result is: "的打印语句首先执行,filter() 和 peek() 方法根本不执行。当最后一条打印语句中的终端操作 collect() 执行时,这些方法才会被执行。

关于 Java 流需要记住的要点

以下是一些用户必须了解的重要要点。

  • 流是元素的序列,可以使用操作管道进行处理或修改。它们不是数据结构。
  • 流支持两种操作类型:一种是中间操作,另一种是终端操作。中间操作会过滤或转换流,而终端操作会产生副作用或结果。
  • 流的设计方式使其具有惰性,这意味着元素处理会在有需求时发生。
  • Java 流并非适用于所有场景。在某些情况下,使用循环进行传统迭代可能更有效和更合适。