Stream解析(转载)
Java从8开始,不但引入了Lambda表达式,还引入了一个全新的流式API:Stream API。它位于java.util.stream
包中。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
Stream和IO包下的InputStream和OutputStream一样吗?
划重点:这个Stream
不同于java.io
的InputStream
和OutputStream
,它代表的是任意Java对象的序列。两者对比如下:
java.io | java.util.stream | |
---|---|---|
存储 | 顺序读写的byte 或char |
顺序输出的任意Java对象实例 |
用途 | 序列化至文件或网络 | 内存计算/业务逻辑 |
这时候大家可能又有疑问了,那么既然是顺序输出的任意Java对象实例,那么和List集合不就相同了吗?
再次划重点:这个Stream
和List
也不一样,List
存储的每个元素都是已经存储在内存中的某个Java对象,而Stream
输出的元素可能并没有预先存储在内存中,而是实时计算出来的。
换句话说,List
的用途是操作一组已存在的Java对象,而Stream
实现的是惰性计算,两者对比如下:
java.util.List | java.util.stream | |
---|---|---|
元素 | 已分配并存储在内存 | 可能未分配,实时计算 |
用途 | 操作一组已存在的Java对象 | 惰性计算 |
关于惰性计算在下面的章节中可以看到。
2|0Stream特点
Stream接口还包含几个基本类型的子接口如IntStream, LongStream 和 DoubleStream。
特点:
- 不存储数据:流是基于数据源的对象,它本身不存储数据元素,而是通过管道将数据源的元素传递给操作。
- 函数式编程:流的操作不会修改数据源,例如
filter
不会将数据源中的数据删除。 - 延迟操作:流的很多操作如filter,map等中间操作是延迟执行的,只有到终点操作才会将操作顺序执行。
- 纯消费:流的元素只能访问一次,类似Iterator,操作没有回头路,如果你想从头重新访问流的元素,对不起,你得重新生成一个新的流。
3|0Stream的创建
Stream的创建有多种方式,下面给大家一一列举出来
3|11、Stream.of()
这种方式一般不常用的,但是测试的时候比较方便
关于Lambda表达式,在我的这篇博客中有详细介绍,感兴趣的朋友可以去看一下
3|22、基于数组或者Collection
这两种创建Stream的方式是我们工作中经常会用到的方式,借助Stream(转化、聚合等方法)可以帮助我们更方便的去输出我们想要的结果
3|33、其他方式
-
使用流的静态方法,比如
Stream.of(Object[])
,IntStream.range(int, int)
或者Stream.iterate(Object, UnaryOperator)
,如Stream.iterate(0, n -> n * 2)
,或者generate(Supplier<T> s)
如Stream.generate(Math::random)
。 -
BufferedReader.lines()
从文件中获得行的流。 -
Files
类的操作路径的方法,如list
、find
、walk
等。 -
随机数流
Random.ints()
。 -
其它一些类提供了创建流的方法,如
BitSet.stream()
,Pattern.splitAsStream(java.lang.CharSequence)
, 和JarFile.stream()
。 -
更底层的使用
StreamSupport
,它提供了将Spliterator
转换成流的方法。
4|0Stream常用API(中间操作)
还记得我们在前面介绍Stream的时候提到了一个惰性计算。惰性计算的特点是:一个Stream
转换为另一个Stream
时,实际上只存储了转换规则,并没有任何计算发生。中间操作会返回一个新的流,它不会修改原始的数据源,而且是由在终点操作开始的时候才真正开始执行。
4|11、distinct
distinct
保证输出的流中包含唯一的元素,它是通过Object.equals(Object)
来检查是否包含相同的元素。
4|22、filter
从字面看是过滤的意思,过滤掉不满足条件的数据
4|33、map
map方法可以将流中的值映射成另外的值,比如将字符串全部转化成小写
从输出结果我们可以看到,字符串全部转化成小写字符了
4|44、limit
limit方法指定流的元素数列,类似于Mysql中的limit方法
4|55、peek
有没有发现出一些东西?
我们将这段代码用上面的map方法实现一下
peek方法的定义如下:
peek方法接收一个Consumer的入参。了解λ表达式的应该明白 Consumer的实现类 应该只有一个方法,该方法返回类型为void。
而map方法的入参为 Function。
我们发现Function 比 Consumer 多了一个 return。这也就是peek 与 map的区别了。
4|66、skip
skip
返回丢弃了前n个元素的流,如果流中的元素小于或者等于n,则返回空的流。
4|77、sorted
sorted()
将流中的元素按照自然排序方式进行排序
如果我们直接调用sorted()方法,那么将按照自然排序,如果我们希望元素按照我们想要的结果来排序,需要自定义排序方法,sorted(Comparator<? super T> comparator)
可以指定排序的方式。如果元素没有实现Comparable
,则终点操作执行时会抛出java.lang.ClassCastException
异常。
5|0Stream常用API(终点操作)
5|11、max、min、count
max:获取最大值
min:获取最小值
count:返回流的数量
5|22、reduce
reduce操作可以实现从一组元素中生成一个值,max()
、min()
、count()
等都是reduce操作,将他们单独设为函数只是因为常用。reduce()
的方法定义有三种重写形式:
5|33、count
获取Stream数量
5|44、Match
anyMatch表示,判断的条件里,任意一个元素成功,返回true
allMatch表示,判断条件里的元素,所有的都是,返回true
noneMatch跟allMatch相反,判断条件里的元素,所有的都不是,返回true