Java8 从 循环(Loops) 到 流(Stream)
这篇文章是是我看到的几篇文章的总结。
Java8 里函数式编程的特性被引入已经成为了这场游戏的转折点。是时候学习一下了。流是函数式编程引入的一大特性

下面我们来一起看一下流的引入能够给我们带来怎样的效果。
Let the coding begin!
首先我们有一个 Article 类,有属性 title,author,和 tags
1 | private class Article { |
下面每一个示例都包含传统的 for 循环和新的 stream 的用法
1.找出集合中第一篇包含标签 “Java” 的文章
传统 for 循环
1 | public Article getFirstJavaArticle() { |
现在我们来用 Stream Api 尝试一下
1 | public Optional<Article> getFirstJavaArticle() { |
是不是很 cool,首先我们使用 filter 来筛选出 tags 中包含 “Java” 的文章,然后我们用 findFirst() 来找出第一个出现的。实际上流是很懒的,他只需要找出一个来,后面就不再处理了。
2.现在我们要匹配所有的元素,而不仅仅是第一个了。
首先,传统的 for 循环
1 | public List<Article> getAllJavaArticles() { |
Stream 操作
1 | public List<Article> getAllJavaArticles() { |
cool,几乎和上面一样的操作,而且我们并不需要显式的声明一个 List,并且在符合条件的时候 add。Stream 提供了一个非常优雅的收集符合条件元素的办法 collect(Collectors.toList())。
到目前为止还没有很惊艳的操作,我们来尝试一下更加惊艳的操作!
3.根据作者分组
首先还是传统的办法
1 | public Map<String, List<Article>> groupByAuthor() { |
那我们能不能用 Stream 做的更简单呢
1 | public Map<String, List<Article>> groupByAuthor() { |
炒鸡棒啊,我们用了 groupingBy() 和 getAuthor 这个引用,就完成了这么复杂的操作并且简洁清晰,可读。
4.获取所有的标签
for 循环
1 | public Set<String> getDistinctTags() { |
Stream
1 | public Set<String> getDistinctTags() { |
flatMap 给我们提供了一种去重的简单办法。
这仅仅是表面而已,我们还有更高级的用法,比如并行等操作。
接下来我还要继续讲解一下如何改变我们以前写的 for (int i=0;… 循环
这个东西就是 IntStream
IntStream 是原始类型 int 的 stream。这样的好处就是减少了拆箱装箱的操作。他是 java.util.stream 包里的,当然这个包里也有处理 double, long 等类型对应的 stream。他们原理是一样的不再赘述。
5.创建 IntStream
创建 IntStream 有很多中办法
其一是使用 of()
1 | IntStream.of(1, 2, 3); |
这样创建好之后我们可以直接使用 forEach() 打印出这些数字,就像前面说到的 Stream 的用法一样。
1 | IntStream.of(1, 2, 3).forEach(System.out::println); |
其二是使用 range() 或者 rangeClosed()
1 | IntStream.range(1, 3); |
那如果我们使用偶数怎么办,也简单
1 | IntStream.iterate(0, i -> i + 2).limit(3); |
iterate(0, i -> i + 2) 创建了一个无限流,limit(3) 限制了数量是3
最后一个要介绍的是 generate()
1 | IntStream.generate(() -> ThreadLocalRandom.current().nextInt(10)).limit(3); |
generate() 很像 iterator,但是又不根据前一个元素去计算
6.关于 IntStream 更多的玩法
使用 map()
1 | IntStream.range(1, 5).map(i -> i * i); |
如果我们需要得到其他类型的流怎么办
1 | Stream<Color> stream = IntStream.range(1, 5).mapToObj(i -> getColor(i)); |
Java 编程思想中作者提到,原始的 foreach 中,使用 range 会降低效率
1 | public class ForEachInt { |
经过测试
1 | n = 100000 |
IntStream 一直很稳定,for(int i = 0; i < n; i++ ) 比 for(int i : range(n)) 要快很多
使用 boxed() 方法 将 IntStream 转换成 Stream
1 | Stream<Integer> stream = IntStream.range(1, 5).boxed(); |
还可以这样,DoubleStream 和 LongStream 也是原始类型的流 double 和 long
1 | DoubleStream stream = IntStream.range(1, 5).mapToDouble(i -> i); |
使用 anyMatch() 判断至少有一个偶数
1 | IntStream.range(1, 5).anyMatch(i -> i % 2 == 0); |
还有
1 | IntStream.range(1, 5).allMatch(i -> i % 2 == 0); |
继续 filter
1 | IntStream.range(1, 5) |
获取最大最小值
1 | IntStream.range(1, 5).max().getAsInt(); |
返回类型是 OptionalInt,就像是 Optional 一样,可以返回 null。这个部分单独讨论
接下来,excellent reduce function
1 | IntStream.range(1, 5).reduce(1, (x, y) -> x * y) |
并行
1 | IntStream.range(1, 5).parallel().forEach(i -> heavyOperation()); |
heavyOperation() 可以是一些费时的操作,这样就可以进行并行计算了
哇,写了这么多!
无限可能。Java11 应该更厉害
参考文献
Java 8: No more loops
Java 8: Replace traditional for loops with IntStreams
https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html