Stream是Java 8中引入的一个新的抽象层。
Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。
Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多
使用流,可以以类似于SQL语句的声明方式来处理数据。例如,以下SQL语句
SELECT max(salary),employee_id,employee_name FROM Employee
上述SQL表达式自动返回最高受薪雇员的详细信息,而不需要客户端做任何事情。在Java中使用集合框架,开发人员必须使用循环并进行重复检查。
另一个问题是效率;由于现在的电脑基本都是多核处理器,因此Java开发人员可以编写并行代码处理,但是往往会出错
为了解决这些问题,Java 8引入了流的概念,让开发人员以声明方式处理数据,并利用多核架构,而无需为其编写任何特定的代码。
(代码简洁+多核处理)
stream并不是某种数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,它只是数据源的一种视图。这里的数据源可以是一个数组,Java容器或I/O channel等。正因如此要得到一个stream通常不会手动创建,而是调用对应的工具方法,比如:
- 调用
Collection.stream()
或者Collection.parallelStream()
方法
- 调用
Arrays.stream(T[] array)
方法
常用的四种stream接口继承关系如下图:
图中4种stream接口继承自
BaseStream
,其中
IntStream, LongStream, DoubleStream
对应三种基本类型(
int, long, double
,注意不是包装类型),
Stream
对应所有剩余类型的stream视图。为不同数据类型设置不同stream接口,可以1.提高性能,2.增加特定接口函数
你可能会奇怪为什么不把
IntStream
等设计成
Stream
的子接口?毕竟这接口中的方法名大部分是一样的。答案是这些方法的名字虽然相同,但是返回类型不同,如果设计成父子接口关系,这些方法将不能共存,因为Java不允许只有返回类型不同的方法重载。
虽然大部分情况下stream是容器调用
Collection.stream()
方法得到的,但stream和collections有以下不同:
- 无存储。stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。
- 为函数式编程而生。对stream的任何修改都不会修改背后的数据源,比如对stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新stream。(重新生成+副本)
- 惰式执行。stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。(中间操作和结束操作)
- 可消费性。stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。
count = strings.stream().filter(string -> string.isEmpty()).count();//filter里面是定义好的规则 其参数就是predicate
System.out.println("Empty Strings: " + count);
count = strings.stream().filter(string -> string.length() == 3).count();
System.out.println("Strings of length 3: " + count);
每次使用都是重新调用stream()
对stream的操作分为为两类,中间操作(intermediate operations)和结束操作(terminal operations),二者特点是:
- 中间操作总是会惰式执行,调用中间操作只会生成一个标记了该操作的新stream,仅此而已。
- 结束操作会触发实际计算,计算发生时会把所有中间操作积攒的操作以pipeline的方式执行,这样可以减少迭代次数。计算完成之后stream就会失效。下表汇总了
Stream
接口的部分常见方法:
操作类型 |
接口方法 |
中间操作 |
concat() distinct() filter() flatMap() limit() map() peek()
skip() sorted() parallel() sequential() unordered() |
结束操作 |
allMatch() anyMatch() collect() count() findAny() findFirst()
forEach() forEachOrdered() max() min() noneMatch() reduce() toArray() |
为什么不在集合类实现这些操作,而是定义了全新的Stream API?Oracle官方给出了几个重要原因:
一是集合类持有的所有元素都是存储在内存中的,非常巨大的集合类会占用大量的内存,而Stream的元素却是在访问的时候才被计算出来,这种“延迟计算”的特性有点类似Clojure的lazy-seq,占用内存很少。
二是集合类的迭代逻辑是调用者负责,通常是
for
循环,而Stream的迭代是隐含在对Stream的各种操作中,例如
map()
。
对于基本数值型,目前有三种对应的包装类型 Stream:IntStream、LongStream、DoubleStream。
关键字:
filter:是一个中间操作,接受一个predicate接口类型的变量,并将所有流对象中的元素进行过滤。filter(s -> s.getState()==State.pay)
map:是一个对于流对象的中间操作,通过给定的方法,它能够把流对象中的每一个元素对应到另外一个对象上。map(s -> s.getPlanNo()) / map(s -> Plan::planNo) / 价格变成 10倍 map(s -> s.getPrice().multiply(BigDecimal.valueOf(10)))
reduce:把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),返回单个的结果值,并且reduce操作每处理一个元素总是创建一个新值
BigDecimal total = stream().reduce(BigDecimal.zero, (a,b) -> a.add(b)); 或
BigDecimal total = stream().reduce(BigDecimal.ZERO, BigDecimal::add)
limit : 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素
sorted: 一个中间操作,能够返回一个排过序的流对象的视图。流对象中的元素会默认按照自然顺序进行排序,除非你自己指定一个Comparator接口来改变排序规则.
collect: 修改现存的值
Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作
groupingBy 按规则分组:stream().collect(Collectors.groupingBy(p->p.getState()))
partitioningBy 是一种特殊的 groupingBy,它依照条件测试的是否两种结果来构造返回的数据结构,get(true) 和 get(false) 能即为全部的元素对象。
Stream 有三个 match 方法,从语义上说:
allMatch:Stream 中全部元素符合传入的 predicate,返回 true
anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true