Java Stream API 是 Java 8 引入的函数式编程API使用stream前:
List
使用stream后:
List
Stream的优势:
- 提升性能:stream会记录下过程操作、并对这些操作进行叠加,最后在一个迭代循环中执行所有叠加的操作,减少迭代次数
- 代码简洁:函数式编程写出的代码简洁且意图明确,使用stream接口让你从此告别for循环。
- 多核友好:只需要调用一下parallel()方法,即可实现并行程序,简化编码
流式操作中使用到了很多内置函数式接口,我们使用的时候只要使用Lambda表达式实现这些内置函数式接口就行
Java8 函数式编程 / Lambda表达式 之前已介绍过,常见的内置函数式接口如下:
创建Stream
Stream 可以从不同的数据类型创建,尤其是集合(Collection);但不能从Map创建。你可以直接通过调用 List 和 Set 的 stream() 方法和 parallelStream() 方法。其中 parallenStream() 会采用多线程执行。从对象创建stream:
// 从List创建
还可以使用 IntStream LongStream DoubleStream 从基本类型创建 Stream。基本类型stream支持一些特殊的“最终结果API”,比如 sum() average() max();
IntStream
普通对象 Stream 可以通过 mapToInt() mapToLong() mapToDouble() 转换成基本类型
Stream
基本类型可以通过 mapToObject() 转换成普通对象 Stream:
IntStream
Stream操作
stream操作的特点:
- non-interfering:stream操作不会修改原始的数据。比如文章开始的例子,stream操作不会改变 myList,迭代结束之后,myList 还是保持着原来的样子。
- stateless:操作都是无状态的,不依赖外面的变量。因此在stream操作内引用外部非final变量会报异常。
- stream中会记录下过程操作、并对这些操作进行叠加,最后在一个迭代循环中执行所有叠加的操作
对stream的操作分为为两类:
- 中间操作:总是会惰式执行,调用中间操作只会生成一个标记了该操作的新stream,仅此而已。中间操作的结果扔是Stream,可以继续使用 Stream API 连续调用;
- 无状态(Stateless)操作:元素的处理不受之前元素的影响
- 有状态(Stateful)操作:该操作只有拿到所有元素之后才能继续下去
- 结束操作:会触发实际计算,计算发生时会把所有中间操作积攒的操作以pipeline的方式执行,这样可以减少迭代次数。结束操作的结果要么是 void ,要么是一个非 Stream 结果;计算完成之后stream就会失效。
- 短路:遇到某些符合条件的元素就可以得到最终结果
- 非短路操作:必须处理所有元素才能得到最终结果。
常用中间操作
Filter函数原型为Stream<T> filter(Predicate<? super T> predicate)作用是过滤出满足predicate条件的元素
List
Sorted函数原型为Stream<T> sorted(Comparator<? super T> comparator)作用是对列表中的元素排序。
List
Map函数原型为Stream<R> map(Function<? super T,? extends R> mapper)作用是对容器中的每个元素按照mapper操作进行转换,转换前后Stream中元素的个数不会改变,但元素的类型取决于转换之后的类型。
List
flatMapmap 方法只能把一个对象转换成另一个对象;如果需要将一个对象转换成多个,则需要用 flatMap。flatMap 函数原型为:Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper),作用是对每个元素执行mapper指定的转换操作,转换前后元素的个数和类型都可能会改变。
Stream
常用结束操作
ForEach函数原型为void forEach(Consumer<? super T> action)作用是对每一个元素的执行action操作
List
Collectorcollect 方法接收一个 Collector,Collector 可以将 Stream 转换成集合结果,比如 List Set Map。JDK内置了常用的 Collector,所以大多数情况下我们不需要自己实现:
List
将 Stream 元素转换成 map 的时候,需要特别注意:key 必须是唯一的,否则会抛出 IllegalStateException 。但是我们可以传入一个 merge function,来指定重复的元素映射的方式:
List
定义 Collector,需要提供4个入参:
- supplier:Supplier<R> // 创建新的结果容器
- accumulator:BiConsumer<R, T> // 将元素添加到结果容器
- combiner:BinaryOperator<R> // 将两个并行执行的结果容器合并为一个结果容器。并行执行(parallelStream)时才会用到,非并行执行(stream)不会被调用到
- finisher:Function<A, R> // 对结果容器变换为整个集合操作的最终结果使用
List
Reducereduce操作可以实现从一组元素中生成一个值,sum()、max()、min()、count()等都是reduce操作,将他们单独设为函数只是因为常用。Reduce 的函数原型为 Optional reduce(BinaryOperator accumulator), 作用是可以将所有的元素变成一个结果,该操作结果会放在一个Optional变量里返回。reduce()的方法定义有三种重载形式:主要的参数 accumulator 用于把多个元素合并成一个;其他多的参数只是为了指明初始值(参数identity),或者是指定并行执行时多个部分结果的合并方式(参数combiner)。
- Optional<T> reduce(BinaryOperator<T> accumulator)
- reduce(T identity, BinaryOperator<T> accumulator)
- U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
第一种可以将 Stream 中的元素聚合成一个:
List
第二种可以多接收一个初始对象。通常可以用于聚合操作(比如累加)。
int
第三种可以多接收一个初始对象和一个并行操作时的combiner函数。
// 求单词长度之和
Match用来判断某一种规则是否与流对象匹配。所有的匹配操作都是终结操作,只返回一个boolean类型的结果。
boolean
Parallel Streams流操作可以是顺序的,也可以是并行的。顺序操作通过单线程执行,而并行操作则通过多线程执行。Parallel Stream 底层使用公共的 ForkJoinPool 来并行计算,底层的真正的线程数据取决于 CPU 的核数,默认是3。Collections 可以通过 parallelStream() 来创建一个并行执行的 Stream;也可以在普通的 Stream 上执行 parallel() 来转换成并行执行的 Stream。
下面这个例子,将并行执行的每一步的线程执行者打印出来:
Arrays
输出如下,展示了每一步都是由哪一个线程来执行的:
filter:
由于Parallel Stream 底层使用的通用的 ForkJoinPool,所以需要注意不要在并行的 Stream 中出现很慢或阻塞的操作,这样会影响其他并行任务。
参考
- https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html
- https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
- https://www.kawabangga.com/posts/3698