Stream特点
- Stream的一系列操作组成了Stream的流水线, Stream流水线包含:
- 数据源: 这里的数据源可能是集合/数组, 可能是生成器, 甚至可能是IO通道(
Files.lines
) - 零个或多个中间操作: 中间操作会导致流之间的转化, 如
filter(Predicate)
- 一个终端操作: 终端操作会产生最终所需要的结果, 或是对原有的数据结构产生影响, 如
count()
可以获得流中数据的长度, 而foreach(Consumer)
可以对流中所有的数据依次操作
- 数据源: 这里的数据源可能是集合/数组, 可能是生成器, 甚至可能是IO通道(
- Stream流是java中函数编程的一个典型代表, 它具有函数式编程的所有优点, 主要包括:
- 延迟计算: 在终端操作前, 所有的操作都不会被真正执行, 只有真正需要计算内容才会被执行, 提高了性能
- 代码简洁: 函数式编程的api调用代码简洁且意图明确, 其可读性远高于for循环
- 底层优化: 底层优化师延迟计算所附带的一个优势, 在执行终端操作前, 底层可以优化执行顺序及执行过程, 以达到最高的执行效率
- 多核封装: Stream流的单核操作和多核操作共享一套接口, 使多核编程非常易用
- Stream的使用与Collection的使用非常相似, 但Stream与Collection有本质上的不同:
- 无存储性: Stream流不存储任何数据, 它只是数据的一个视图
- 不可变性: 对Stream的操作不会修改其背后的数据源, 而是产生一个新的Stream 实际上为了方便, 有部分的api会对原数据修改
- 惰性执行: Collection中的数据消费的结果可以立即体现, 而Stream真正需要执行的时候才会执行
- 单次消费: Stream中的数据仅能被消费一次, 一次遍历后就会失效, 若要二次消费则需重新生成Stream
Stream的实现
常用的Stream接口继承关系如下图:
如上图, Stream
接口继承于BaseStream
; 主要可以分为针对基本类型的LongStream
/IntStream
/DoubleStream
和针对Object
实现的通用的Stream
接口; 这样既保证了面向对象的操作, 也保证了对基本类型的兼容
Stream的基本使用
中间操作
filter
filter
用于根据predicate
中定义的方法来过滤集合中的元素, 如下面的例子integer -> integer > 3
就过滤掉了小于或等于3
的元素
public void filter() {
Arrays.asList(1, 2, 3, 4, 5, 6, 7)
.stream()
.filter(integer -> integer > 3)
.forEach(System.out::println);
}
map
map的功能是对Stream中的元素进行转换, 转换规则为传入的mapper
函数; 转换过程不会改变元素的数量, 只会改变元素的类型和值; 下面的例子中, 将所有字符都包裹上了尖括号
public void map() {
Arrays.stream("It's a wonderful day for me".split(" "))
.map(s -> "<" + s + ">")
.forEach(System.out::println);
}
flatMap
flatMap
类似于map
; 但是他会将mapper
返回的的流"摊平"(flatten); 此过程不同于map
, 它会改变元素的数量, 同时也会改变元素的类型和值; 下面的例子会将原列表中所有的数字列表摊平, 并形成一个新的流, 即一个包含1-7所有数字的流
public void flatMap() {
Arrays.asList(Collections.singleton(1), Arrays.asList(2, 3, 4), Set.of(5,6,7))
.stream()
.flatMap(Collection::stream)
.forEach(System.out::println);
}
终端操作
forEach
forEach
是对容器中的每个元素执行传入的Consumer
的操作; 如下面的例子, 传入的是System.out::println
, 因此会打印每个元素
public void foreach() {
Arrays.asList(1, 2, 3, 4, 5, 6, 7)
.stream()
.forEach(System.out::println);
}
sorted
sorted
函数有两个, 一个是按照自然顺序排序, 另一个是使用自定义比较器排序, 对应的是Comparator.naturalOrder()
其底层会调用Comparable.compareTo
; 下面的例子对字符串的长度记性升序排序
public void sorted() {
Arrays.stream("It's a wonderful day for me".split(" "))
.sorted((s1, s2) -> s1.length() - s2.length())
.forEach(System.out::println);
}
reduce
reduce
操作可以从一组元素中规约生成一个值, 它可以实现多种操作, 在java流中max
, min
, sum
, count
的本质都是规约操作; 下面是规约操作的定义, 它通过一个初始值identity
进行累加, 最后生成一个值, 在并发并发情况下, 还可能会需要对多个累加的值进行合并
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
max
下面是对reduce
的一个最基本的使用, 找到长度最大的字符串的值; 它从第一个元素开始累计, 每次对比两个元素并取较大的元素; 如此进行累加, 最后得到的便是最大的元素
System.out.println(Stream.of("I", "love", "you", "too")
.reduce((s1, s2) -> s1.length() >= s2.length() ? s1 : s2)
.orElse(null));
System.out.println(Stream.of("I", "love", "you", "too")
.max((s1, s2) -> s1.length() - s2.length())
.orElse(null));
sum
max
无需指定幺元identity
和combiner
因为它是从第一个元素开始累加的; 其会从第一个元素开始累积, 并且会调用acuumulator
进行combine
; 而sum
方法需要自己指定初始值为0
, 并指定combiner
的逻辑为两个分段的相加, 如下面的例子计算流中的字符数
public void totalLength() {
System.out.println(Stream.of("I", "love", "you", "too")
.reduce(0
, (total, s) -> total + s.length()
, (a, b) -> a + b));
}