Stream
流的概念
Stream是Java 8 中新增的用来补充集合类的一个新的接口,主要的目的是为了增强Java原有的集合类{Collection}的功能,特别是函数式相关的功能,为了理解Java里的Stream接口,首先我们得先理解什么是Stream,即什么是流
什么是流
Stream可以说就是数据流{类比水流},然后有一个数据来源{水流的源头},然后这个流中的数据元素有可能是有限的{杯壁下流},也可能是无限的{长江水流},我们举几个例子:
有限的流
- 一个集合,例如ArrayList中的所有元素
- 一个固定文件(不支持更新)中的所有字符
无限的流
- 日志更新的文件
- 一个不关闭的监听端口传来的数据
注意,流是可以有顺序,可以没顺序的
Stream接口
Java的Stream接口就集合了相关对数据流的操作。
这些操作大部分都是上节课我们所说的高阶函数,然后这个接口的操作有一些基本特性:
- 不存储数据:流是基于数据源的映射,其本身并不存储数据元素,而是通过类似管道的机制把数据源的元素传递给操作。
- 不修改数据源:流的操作都不会修改数据源,而是返回新的数据。(函数式编程里很重要的一个特性,immutable,不可变数据)
- 惰性执行:流的操作分为两种,第一种为中间操作,另一种为规约和收集操作,
中间操作只是定义对流的响应操作链,但是不会真正执行,只有最后规约和收集的时候才会真正执行中间操作。(惰性执行) - 可以解绑:对于流,我们可以限制其中遍历的元素个数,比如limit(n)和findFirst()这样的操作,都会得到结构或者达到限制之后马上返回。
- 纯消费:流的元素只能访问一次,操作没有回头路,如果需要重新操作,则重新创建一个流。
问题:为什么要把数据转换成Stream再传递给操作,直接把数据源传递给操作不是更直接?
为了使代码的执行有明了的一系列特性:
为了使程序可以惰性执行,
便于解绑,把数据传递给一个或若干个操作,可以得到结果或者达到限制后马上返回,
且一方面对数据源有保护作用
不修改源数据也会减少很多并发问题
并行流和串行流
在Java中,默认创建的都是串行流,比如使用Collection.stream() 返回的就是创建的串行流,
而调用Collction.parallelStream()则会返回创建的并行流
无状态
对于流中不同元素的操作应该是相互独立的,因为Java并不保证不同元素之间的执行顺序,甚至不保证他们的操作是在一个线程中的。
流的操作
流的创建
1.通过集合的stream()方法或者parallelStream()方法;
2.通过Arrays.stream(Object [])方法;
3.使用Stream的静态方法,比如
1.Stream.of(Object[]),IntStream.range(int,int)
2.Stream.iterate(Object,UnaryOperator),如Stream.iterate{1,n->n*2}很明显,这是个无限流1,2,4,8,16
3.Stream.generate(Suppler
4.BufferReader.lines()从文件中获得行的流
5.随机数流Random.ints()
6.其他一些类提供了创建流的方法
7.底层使用StreamSupport(),它提供了将Sliterator转换成流的方法
中间操作
中间操作类似于对一个已有的流,绑定一个高阶(定义对每个元素的而操作的操作)的操作,
然后返回一个新的流. 其中只是绑定这个操作, 并不会马上执行, 当然也不会修改原始的数据源.
forEach之前不执行中间操作
终点操作
终点操作会触发中间的操作,最终生成结果
两个冒号表示方法引用
问题:为什么要有方法引用?直接用原来的方法不可以吗?
- 看起来简洁一点
1 | Stream.of("a231","b281827","c2312","b11", "cs") |
等同于1
2
3
4Stream.of("a231","b281827","c2312","b11", "cs")
.distinct()
.sorted((x, y) -> x.length() - y.length())
.forEach(x->System.out.println(x));
函数式编程
- 不要去从Java去学函数式编程
- 也不要用Java的概念去理解函数式编程
- Haskell, Scala, Kotlin