街机之三国战记:2025年5月26日开启的史诗级三国争霸赛 2025-05-26 04:19:05
纳兹冒险记:勇者集结,奇幻大陆大冒险 2025-04-20 15:58:56
世界杯阿根廷阵容全面解析 洛夫朗与梅西携手争冠之路 2025-09-28 08:43:54
lol舞动巅峰萨科怎么样?值得买吗? 2025-12-26 23:30:00
猎魔无双:2025年5月10日开启的全新跨服竞技大乱斗活动 2025-05-10 00:53:51
一起来捉妖金花狮怎么样 妖灵技能属性图鉴介绍 2025-10-03 14:12:09
《玛雅战纪》2025盛夏狂欢盛典:探索失落文明,赢取神秘黄金宝藏! 2025-06-05 22:45:50
十三味菥蓂胶囊(冈底斯峰) 2025-11-22 05:45:58
红双喜(一带一路纪念版)价格图表 2025-12-13 20:18:08
十二地支六冲详解 2025-12-06 01:09:55

使用Stream

廖雪峰

资深软件开发工程师,业余马拉松选手。

Java从8开始,不但引入了Lambda表达式,还引入了一个全新的流式API:Stream API。它位于java.util.stream包中。

划重点:这个Stream不同于java.io的InputStream和OutputStream,它代表的是任意Java对象的序列。两者对比如下:

java.io

java.util.stream

存储

顺序读写的byte或char

顺序输出的任意Java对象实例

用途

序列化至文件或网络

内存计算/业务逻辑

有同学会问:一个顺序输出的Java对象序列,不就是一个List容器吗?

再次划重点:这个Stream和List也不一样,List存储的每个元素都是已经存储在内存中的某个Java对象,而Stream输出的元素可能并没有预先存储在内存中,而是实时计算出来的。

换句话说,List的用途是操作一组已存在的Java对象,而Stream实现的是惰性计算,两者对比如下:

java.util.List

java.util.stream

元素

已分配并存储在内存

可能未分配,实时计算

用途

操作一组已存在的Java对象

惰性计算

Stream看上去有点不好理解,但我们举个例子就明白了。

如果我们要表示一个全体自然数的集合,显然,用List是不可能写出来的,因为自然数是无限的,内存再大也没法放到List中:

List list = ??? // 全体自然数?

但是,用Stream可以做到。写法如下:

Stream naturals = createNaturalStream(); // 全体自然数

我们先不考虑createNaturalStream()这个方法是如何实现的,我们看看如何使用这个Stream。

首先,我们可以对每个自然数做一个平方,这样我们就把这个Stream转换成了另一个Stream:

Stream naturals = createNaturalStream(); // 全体自然数

Stream streamNxN = naturals.map(n -> n.multiply(n)); // 全体自然数的平方

因为这个streamNxN也有无限多个元素,要打印它,必须首先把无限多个元素变成有限个元素,可以用limit()方法截取前100个元素,最后用forEach()处理每个元素,这样,我们就打印出了前100个自然数的平方:

Stream naturals = createNaturalStream();

naturals.map(n -> n.multiply(n)) // 1, 4, 9, 16, 25...

.limit(100)

.forEach(System.out::println);

我们总结一下Stream的特点:它可以“存储”有限个或无限个元素。这里的存储打了个引号,是因为元素有可能已经全部存储在内存中,也有可能是根据需要实时计算出来的。

Stream的另一个特点是,一个Stream可以轻易地转换为另一个Stream,而不是修改原Stream本身。

最后,真正的计算通常发生在最后结果的获取,也就是惰性计算。

Stream naturals = createNaturalStream(); // 不计算

Stream s2 = naturals.map(n -> n.multiply(n)); // 不计算

Stream s3 = s2.limit(100); // 不计算

s3.forEach(System.out::println); // 计算

惰性计算的特点是:一个Stream转换为另一个Stream时,实际上只存储了转换规则,并没有任何计算发生。

例如,创建一个全体自然数的Stream,不会进行计算,把它转换为上述s2这个Stream,也不会进行计算。再把s2这个无限Stream转换为s3这个有限的Stream,也不会进行计算。只有最后,调用forEach确实需要Stream输出的元素时,才进行计算。我们通常把Stream的操作写成链式操作,代码更简洁:

createNaturalStream()

.map(n -> n.multiply(n))

.limit(100)

.forEach(System.out::println);

因此,Stream API的基本用法就是:创建一个Stream,然后做若干次转换,最后调用一个求值方法获取真正计算的结果:

int result = createNaturalStream() // 创建Stream

.filter(n -> n % 2 == 0) // 任意个转换

.map(n -> n * n) // 任意个转换

.limit(100) // 任意个转换

.sum(); // 最终计算结果

小结

Stream API的特点是:

Stream API提供了一套新的流式处理的抽象序列;

Stream API支持函数式编程和链式操作;

Stream可以表示无限序列,并且大多数情况下是惰性求值的。