Stream 为什么会出现?
Stream 出现之前,遍历一个集合最传统的做法大概是用 Iterator,或者 for 循环。这种两种方式都属于外部迭代,然而外部迭代存在着一些问题。
开发者需要自己手写迭代的逻辑,虽然大部分场景迭代逻辑都是每个元素遍历一次。
如果存在像排序这样的有状态的中间操作,不得不进行多次迭代。
多次迭代会增加临时变量,从而导致内存的浪费。
虽然 Java 5 引入的 foreach 解决了部分问题,但也引入了新的问题。
foreach 遍历不能对元素进行赋值操作
遍历的时候,只有当前被遍历的元素可见,其他不可见
随着大数据的兴起,传统的遍历方式已经无法满足开发者的需求。
就像小作坊发展到一定程度要变成大工厂才能满足市场需求一样。大工厂和小作坊除了规模变大、工人不多之外,最大的区别就是多了流水线。流水线可以将工人们更高效的组织起来,使得生产力有质的飞跃。
所以不安于现状的开发者们想要开发一种更便捷,更实用的特性。
-
它可以像流水线一样来处理数据
-
它应该兼容常用的集合
-
它的编码应该更简洁
-
它应该具有更高的可读性
-
它可以提供对数据集合的常规操作
-
它可以拼装不同的操作
经过不懈的能力,Stream 就诞生了。加上 lambda 表达式的加成,简直是如虎添翼。
你可以用 Stream 干什么?
下面以简单的需求为例,看一下 Stream 的优势:
从一列单词中选出以字母a开头的单词,按字母排序后返回前3个。
传统实现方式
List<String> list = Lists.newArrayList("are", "where", "advance", "anvato", "java", "abc");
List<String> tempList = Lists.newArrayList();
List<String> result = Lists.newArrayList();
for( int i = 0; i < list.size(); i++) {
if (list.get(i).startsWith("a"))
tempList.add(list.get(i));
}
tempList.sort(Comparator.naturalOrder());
result = tempList.subList(0,3);
Stream实现方式
List<String> list = Arrays.asList("are", "where", "anvato", "java", "abc");
List<String> result =
list.stream() //定义输入源,得到Stage0 Head节点(流)
.filter(s -> s.startsWith("a")) //定义中间操作,得到Stage1 StatelessOp节点(流)
.sorted() //定义中间操作,得到Stage2 StatefulOp节点(流),SortedOps.OfRef实例
.limit(3) //定义中间操作,得到Stage3 StatefulOp节点(流),SliceOps的StatefulOp实例
.collect(Collectors.toList()); //定义终端操作, TerminalOp节点(流),ReduceOp实例
Stream 是怎么实现的?
需要解决的问题:
-
如何定义流水线?
-
原料如何流入?
-
如何让流水线上的工人将处理过的原料交给下一个工人?
-
流水线何时开始运行?
-
流水线何时结束运行?
总观全局
Stream 处理数据的过程可以类别成工厂的流水线。数据可以看做流水线上的原料,对数据的操作可以看做流水线上的工人对原料的操作。
事实上 Stream 只是一个接口,并没有操作的缺省实现。最主要的实现是 ReferencePipeline,而 ReferencePipeline 继承自 AbstractPipeline ,AbstractPipeline 实现了 BaseStream 接口并实现了它的方法。但 ReferencePipeline 仍然是一个抽象类,因为它并没有实现所有的抽象方法,比如 AbstractPipeline 中的 opWrapSink()抽象方法,该方法是由具体的中间操作和终端操作来实现的 。ReferencePipeline内部定义了三个静态内部类,分别是&#