Java Stream API 是 Java 8 引入的函数式编程API使用stream前:

List

使用stream后:

List

Stream的优势:

  • 提升性能:stream会记录下过程操作、并对这些操作进行叠加,最后在一个迭代循环中执行所有叠加的操作,减少迭代次数
  • 代码简洁:函数式编程写出的代码简洁且意图明确,使用stream接口让你从此告别for循环。
  • 多核友好:只需要调用一下parallel()方法,即可实现并行程序,简化编码

流式操作中使用到了很多内置函数式接口,我们使用的时候只要使用Lambda表达式实现这些内置函数式接口就行

Java8 函数式编程 / Lambda表达式 之前已介绍过,常见的内置函数式接口如下:

java Stream转化未带List的map stream list转string_List

创建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就会失效。
  • 短路:遇到某些符合条件的元素就可以得到最终结果
  • 非短路操作:必须处理所有元素才能得到最终结果。

java Stream转化未带List的map stream list转string_java8 list转map_02

常用中间操作

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个入参:

  1. supplier:Supplier<R> // 创建新的结果容器
  2. accumulator:BiConsumer<R, T> // 将元素添加到结果容器
  3. combiner:BinaryOperator<R> // 将两个并行执行的结果容器合并为一个结果容器。并行执行(parallelStream)时才会用到,非并行执行(stream)不会被调用到
  4. 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