Stream
- 一、引入流
- 二、使用流
- 三、Optional取代null
- 四、用流收集数据(collect)
一、引入流
1、一个案例引入
看一个使用Stream
(Java8)和不使用Stream
(Java7)代码量的区别。
这里需要筛选出一份菜单中卡路里<400的菜的名字。
1 | public class Code_01_Java7AndJava8Compare { |
2、流简介
- 流简短的定义: 是数据渠道,用于操作数据源(集合,数组等)所生成的元素序列;
- 元素序列 — 就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序
值。因为集合是数据结构,所以它的主要目的是以特定的时间/ 空间复杂度存储和访问元
素(如ArrayList 与 LinkedList) 。但流的目的在于表达计算,比如你前面见到的filter、 sorted和 map
。集合讲的是数据,流讲的是计算。 - 源 — 流会使用一个提供数据的源,如集合、数组或输入/输出资源;
- 数据处理操作 — 流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中
的常用操作,如filter、 map、 reduce、 find、 match、 sort
等;
- 元素序列 — 就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序
- 流的重要的特点
- 流水线 — 很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大
的流水线;流水线的操作可以看作对数据源进行数据库式查询; - 内部迭代 — 与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的;
- ①
Stream
自己不会存储元素;②Stream
不会改变原对象,相反,他们会返回一个持有结果的新Stream
;③Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行;
- 流水线 — 很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大
3、流与集合
1)、只能遍历一次
请注意,和迭代器类似,流只能遍历一次
- 遍历完之后,我们就说这个流已经被消费掉了;
- 你可以从原始数据源那里再获得一个新的流来重新遍历一遍,就像迭代器一样(这里假设它是集
合之类的可重复的源,如果是I/O通道就没戏了);
2)、外部迭代与内部迭代
- 使用
Collection
接口需要用户去做迭代(比如用for-each
) ,这称为外部迭代; - 相反, Streams库使用内部迭代;
外部迭代和内部迭代的区别:
4、流操作
主要分为两类操作: 中间操作和终端操作。
1)、中间操作
中间操作就是产生的结果(仍然是一个流)。
诸如filter
或 sorted
等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。
1 | // 除非流水线上触发一个终端操作,否则中间操作不会执行任何处理 |
看运行结果,可以发现Stream
是有延迟的性质。
2)、终止操作
终止操作会从流的流水线生成结果。其结果是任何不是流的值,比如List、 Integer,甚至 void。
3)、流的使用步骤
流的流水线背后的理念类似于构建器模式。
三个基本步骤:
- 创建Stream : 需要一个数据源(如:集合,数组),获取一个流;
- 中间操作: 一个中间操作链,对数据源的数据进行处理;
- 终止操作(终端操作): 一个终止操作,执行中间操作链,并产生结果;
二、使用流
1、构建流
构建的流的方式有:
从Collection中构建;
从值
value
(Stream.of()
)中构建;从数组中构建(
Arrays.stream()
);从文件中构建;
由函数生成: 创建无限流;
创建的几种方法的示例代码:
1 | /** 创建流的几种方法 */ |
输出:
1 | --------fromCollection-------- |
2、filter、limit、skip、map、flatMap
- filter : 该操作会接受一个谓词(一个返回boolean的函数)(
Predicate
)作为参数,并返回一个包括所有符合谓词的元素的流; - limit : 流支持
limit(n)
方法,该方法会返回一个不超过给定长度的流; - skip : 流还支持
skip(n)
方法,返回一个扔掉了前n 个元素的流; - map : 流支持map方法,它会接受一个函数(
Function
)作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”); - flatMap (扁平化):
flatmap()
方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接
起来成为一个流;
测试代码:
1 | public class Code_04_StreamOperations1 { |
输出:
1 | -------filterTest--------- |
3、match、find、reduce
- match:查看元素是否匹配(返回boolean),包括
allMatch(), anyMatch()、noneMatch()
; - find :
-
isPresent()
将在Optional包含值的时候返回true, 否则返回false; -
ifPresent(Consumer<T> block)
会在值存在的时候执行给定的代码块; T get()
会在值存在时返回值,否则抛出一个NoSuchElement异常;T orElse(T other)
会在值存在时返回值,否则返回一个默认值;Optional<T> of(T value)
: 通过value构造一个Optional;
-
1 | public class Code_05_StreamOperations2 { |
输出:
1 | -------matchTest--------- |
4、数值流
Java 8引入了三个原始类型特化流接口来解决这个问题:IntStream、 DoubleStream和LongStream
,分别将流中的元素特化为int、 long和 double
,从而避免了暗含的装箱成本。这些特化的原因并不在于流的复杂性,而是装箱造成的复杂性——即类似int和 Integer之间的效率差异。
映射方法:
- 映射到数值流
- 将流转换为特化版本的常用方法是mapToInt、 mapToDouble和 mapToLong;
- 例如mapToInt返回一个IntStream(而不是一个
Stream<Integer>
);
- 转换回对象流
- 使用
boxed()
方法; - 用处: 例如,IntStream上的操作只能产生原始整数。
例子(勾股数):
1 | public class Code_06_NumericStream { |
输出:
1 | -------example1--------- |