Java流式编程-第一章

前言

Java流式编程, 指的是是使用java8新增的一个StreamAPI进行编程

StreamAPI天然契合了函数式编程, 在使用StreamAPI的过程中, 你就已经无时无刻的拥抱着函数式编程

前段时间我对Java函数式编程进行了归纳总结, 所以接下来的任务便是将流式编程也一网打尽.

初识StreamAPI

其实在我工作的第一年期间, 我的所有接触的项目, 它们的jdk版本都不超过1.7, 大多是1.6版本和1.7版本

所以在我工作的第一年期间, 我完全不知道StreamAPI的存在.

一次偶然的契机, 我更换了自己的个人电脑, 所以在新系统上(ubuntu18.04)重新安装jdk的时候, 我率先下载的是jdk1.8

当时正处在换工作期间, 在阅读各种面经的同时, 我也阅读了一些jdk源码.

当我第一次认真阅读Java集合的源码的时候, Collection类的stream()方法进入我的实现.

在软件开发中, stream这个单词, 也就是流这个概念是很常见的, 流在我的印象里通常意味着一个管道, 我们可以对其进行操作, 进行数据处理, 而数据处理是我们平时工作开发最容易遇到的情况, 所以流对于提高我们的代码开发效率是有很大的帮助的.

所以StreamAPI是个值得我们去学习的一个模块.

StreamAPI的底层具体是如何实现的, 我以后也会一一进行探究, 但是本章内容主要还是以学习API, 编写实例为主, 不去探究StreamAPI底层原理是什么

Stream的特点

天然结合函数式编程

Stream的语法就是基于函数式编程做的, 我可以大胆的这么说. 为了证明这一点, 下面的一个实例程序(随机输出7个有序的int数)可以帮助你初步认识StreamAPI

1
2
3
4
5
6
7
8
public static void main(String[] args) {
new Random(47)
.ints(5, 20) // 产生一个5-20区间的int流
.distinct() // 去掉流中重复的int
.limit(7) // 限制只要前7个
.sorted() // 将7个元素进行排序, 当然这个排序默认是递增
.forEach(System.out::println); // 对每个元素依次执行System.out::println
}

上面的代码中, 全程透露了一股函数式编程的味道, 因为它看上去特别简洁, 而且用到了高阶函数和方法引用的语法, 并且每个方法都会返回之前的流给下一个方法使用
, 这看上去就像数据从头”流”到了尾

这个程序用传统的写法应该是这样的

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
Random rand = new Random(47);
SortedSet<Integer> rints = new TreeSet<>();
while(rints.size() < 7) {
int r = rand.nextint(20);
if(r < 5) continue;
rints.add(r);
}
System.out.println(rints);
}

对于工作过几年但是对函数式编程没有一点接触的的java程序员来说, 传统的写法更加的亲切, 但是结果是毋庸置疑的, StreamAPI的写法更加的易阅读和简洁

内部迭代

细心的同学肯定也会发现, 在上面的例子中, forEach方法会自动的替我们完成循环迭代的过程, 而不需要我们编写while循环或者for循环.

我们编写的while循环和for循环是一种外部迭代, 也就是说, 如何迭代的控制权是在我们手上的.

但是在操作集合的数据进行循环迭代的时候, 我们编写的for循环和while循环是千篇一律的, 无非就是从一个index位置的数据循环迭代到另一个index位置的数据.

所以为什么不将这些代码封装起来呢, forEach方法就体现了这一点, 这种循环, 叫内部迭代

内部迭代有好有坏, 坏处是我们没法做更细致化的循环控制, 使用forEach也就是意味着控制权交给了计算机, 由它的底层实现来代替我们自己编写循环逻辑

带来的好处是内部迭代可以利用多核处理器的优势, 通过放弃对迭代过程的控制,我们把控制权交给并行化机制, 而并行往往以为了循环的效率变得更高的.

流是懒加载的

懒加载是软件开发的又一个不错的思想, 流对数据的加载也是懒加载的, 你可以想象所有数据只有在真正使用的使用, 才会从流的开头”流”向结尾

StreamAPI的性能到底如何

这已经有很多前辈进行了测试, Stream在做简单的循环操作时候, 其性能其实比我们自己写迭代要慢, 但是我前面也说了, Stream的内部迭代是可以利用多核处理器的, 所以StreamAPI再进行并行操作的时候, 其速度会远超我们自己手动实现的代码

所以我用此文 的结论:

  • 对于简单操作,比如最简单的遍历,Stream串行API性能明显差于显示迭代,但并行的Stream API能够发挥多核特性。
  • 对于复杂操作,Stream串行API性能可以和手动实现的效果匹敌,在并行执行时Stream API效果远超手动实现。
  • 如果出于性能考虑,1. 对于简单操作推荐使用外部迭代手动实现,2. 对于复杂操作,推荐使用Stream API, 3. 在多核情况下,推荐使用并行Stream API来发挥多核优势,4.单核情况下不建议使用并行Stream API。
  • 出于代码简洁性考虑,使用Stream API能够写出更短的代码

至于为什么Stream在做简单循环操作反而变慢, 这可能和JVM执行函数式编程代码上存在一定的效率问题, 导致简单的循环操作在转义函数式风格代码的时间消费比做真正的逻辑操作还长, 下面是最开始我给出的Random例子在编译后class文件的内容:

1
2
3
4
5
public static void main(String[] args) {
IntStream var10000 = (new Random(47L)).ints(5, 20).distinct().limit(7L).sorted();
PrintStream var10001 = System.out;
var10000.forEach(var10001::println);
}

从内容可以看出函数式编程的语法被保留, 那么JVM是如何去执行它们就是性能问题产生的其中一个要素把(纯属猜测)

总结

本章简单的讲了下StreamAPI的概念, 并没有深入讲解API的内容, 但是本章对于后续的学习是非常重要的一环, 本章阐述了StreamAPI的重要性, 这是我们为什么要学习它们的前提

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

Live2d