Java SE 8 中引入了这样一个库: java.util.stream 包,它有助于为各种数据来源的批量操作建立简明的、声明性的表达式。它是一个精心设计的库,充分利用了Lambda特性,可以将 Streams 视为 Java 中第一个充分利用了Lambda表达式的强大功能的库。
清单1:原来计算一个列表中除去1的总和的代码:
List<Integer> list = Arrays.asList(1,2,3,4,5); int sum = 0; for(Integer i: list){ if(i == 1){ continue; } sum+=i; }
清单2:使用Stream进行计算的代码:
List<Integer> list = Arrays.asList(1,2,3,4,5); int sum=list.stream().filter(t->t!=1).mapToInt(t->t).sum();
流的关注点不是数据,而是计算,流具有很强的数据聚合、汇总能力。一个流管道包含一个流来源、0 或多个中间操作,以及一个终止操作。
使用流进行数据计算可以大致分为三个步骤:
1、由数据源构建流管道,即创建一个Stream
2、执行中间流操作
3、执行终止流操作
构建流管道:
Collection.stream() 使用一个集合的元素创建一个流。 Stream.of(T...) 使用传递给工厂方法的参数创建一个流。 Stream.of(T[]) 使用一个数组的元素创建一个流。 Stream.empty() 创建一个空流。 Stream.iterate(T first, BinaryOperator<T> f) 创建一个包含序列 first, f(first), f(f(first)), ... 的无限流 Stream.iterate(T first, Predicate<T> test, BinaryOperator<T> f) (仅限 Java 9)类似于 Stream.iterate(T first, BinaryOperator<T> f),但流在测试预期返回 false 的第一个元素上终止。 Stream.generate(Supplier<T> f) 使用一个生成器函数创建一个无限流。 IntStream.range(lower, upper) 创建一个由下限到上限(不含)之间的元素组成的 IntStream。 IntStream.rangeClosed(lower, upper) 创建一个由下限到上限(含)之间的元素组成的 IntStream。 BufferedReader.lines() 创建一个有来自 BufferedReader 的行组成的流。 BitSet.stream() 创建一个由 BitSet 中的设置位的索引组成的 IntStream。 Stream.chars() 创建一个与 String 中的字符对应的 IntStream。
中间流操作:
filter(Predicate<T>) 与预期匹配的流的元素 map(Function<T, U>) 将提供的函数应用于流的元素的结果 flatMap(Function<T, Stream<U>> 将提供的流处理函数应用于流元素后获得的流元素 distinct() 已删除了重复的流元素 sorted() 按自然顺序排序的流元素 Sorted(Comparator<T>) 按提供的比较符排序的流元素 limit(long) 截断至所提供长度的流元素 skip(long) 丢弃了前 N 个元素的流元素 takeWhile(Predicate<T>) (仅限 Java 9)在第一个提供的预期不是 true 的元素处阶段的流元素 dropWhile(Predicate<T>) (仅限 Java 9)丢弃了所提供的预期为 true 的初始元素分段的流元素
清单3:返回年龄小于22岁且身高由低到高排名在11-20名的同学的名字
List<String> names=list.stream().sorted(Comparator.comparing(t->t.getHeight())).filter(t->t.getAge()< 22).map(t->t.getName()).skip(10).limit(10).collect(Collectors.toList());
终止流操作:
forEach(Consumer<T> action) 将提供的操作应用于流的每个元素。 toArray() 使用流的元素创建一个数组。 reduce(...) 将流的元素聚合为一个汇总值。 collect(...) 将流的元素聚合到一个汇总结果容器中。 min(Comparator<T>) 通过比较符返回流的最小元素。 max(Comparator<T>) 通过比较符返回流的最大元素。 count() 返回流的大小。 {any,all,none}Match(Predicate<T>) 返回流的任何/所有元素是否与提供的预期相匹配。 findFirst() 返回流的第一个元素(如果有)。 findAny() 返回流的任何元素(如果有)。
聚合操作Stream.reduce()可以将元素序列聚合到该类型的单个元素值。Stream库中有多种reduce()方法,包括了以下两种:
Optional<T> reduce(BinaryOperator<T> op)
T reduce(T identity, BinaryOperator<T> op)
Reduction Operation是一种来自函数编程的技术,它抽象化了许多不同的操作。给定一个类型为 T,包含 n 个元素的非空数列 (x1, x2, ..., xn) 和 T 上的二元运算符(在这里表示为&),reduction opreation 被定义为:(x1&x2&x3...&xn)
二元运算符&需要满足结合性条件:即 ((a&b)&c)==(a&(b&c)),并不需要满足交换性
Optional<T> reduce(BinaryOperator<T> op):只需要确定二元运算符即定义reduction operation的具体操作即可。
清单5:计算总和
int sum = Stream.of(1,2,3,4,5).reduce((a,b)->a+b;).get();
清单6:计算最大值
int max = Stream.of(1,2,3,4,5).reduce(Math::max).get();
T reduce(T identity, BinaryOperator<T> op):使用这个方法时还需要提供一个identity初始值
清单7:字符串串联:
String string = strings.stream().reduce("",String::concat);
Stream.reduce操作是通过某个连接动作将所有元素汇总成一个汇总结果,比如获取数列的和或它的最大值。但是有时我们并不需要单个汇总值;您想将结果组织为类似 List 或 Map 的数据结构,或者将它汇总为多个汇总值。在这种情况下,可以使用collect()方法。
它接受以下 3 个参数:
<R> collect(Supplier<R> resultSupplier, BiConsumer<R, T> accumulator, BiConsumer<R, R> combiner)
Supplier<R> resultSupplier:一种生成空结果容器的途径
BiConsumer<R, T> accumulator:一种将新元素合并到结果容器中的途径
BiConsumer<R, R> combiner:一种合并两个结果容器的途径
清单8:
List<String> names=list.stream().filter(t->t.getAge()>20).map(t->t.getName()).collect(ArrayList::new,ArrayList::add,ArrayList::addAll);
ArrayList::new 创建一个ArrayList的空容器
ArrayList::add 将新元素添加到结果容器中
ArrayList::addAll 合并两个ArrayList容器
Stream同时还有这样一个collect()方法:
<R, A> R collect(Collector<? super T, A, R> collector);
Collector作为参数传入,配合Collectors使用开发更加简便,清单8可以直接调用Collectors.toList()方法
List<String> names=list.stream().filter(t->t.getAge()>20).map(t->t.getName()).collect(Collectors.toList());
Collectors 类常见的聚合操作
toList() 将元素收集到一个 List 中。 toSet() 将元素收集到一个 Set 中。 toCollection(Supplier<Collection>) 将元素收集到一个特定类型的 Collection 中。 toMap(Function<T, K>, Function<T, V>) 将元素收集到一个 Map 中,依据提供的映射函数将元素转换为键值。 summingInt(ToIntFunction<T>) 计算将提供的 int 值映射函数应用于每个元素(以及 long 和 double 版本)的结果的总和。 summarizingInt(ToIntFunction<T>) 计算将提供的 int 值映射函数应用于每个元素(以及 long 和 double 版本)的结果的 sum、min、max、count 和 average。 reducing() 向元素应用缩减(通常用作下游收集器,比如用于 groupingBy)(各种版本)。 partitioningBy(Predicate<T>) 将元素分为两组:为其保留了提供的预期的组和未保留预期的组。 partitioningBy(Predicate<T>, Collector) 将元素分区,使用指定的下游收集器处理每个分区。 groupingBy(Function<T,U>) 将元素分组到一个 Map 中,其中的键是所提供的应用于流元素的函数,值是共享该键的元素列表。 groupingBy(Function<T,U>, Collector) 将元素分组,使用指定的下游收集器来处理与每个组有关联的值。 minBy(BinaryOperator<T>) 计算元素的最小值(与 maxBy() 相同)。 mapping(Function<T,U>, Collector) 将提供的映射函数应用于每个元素,并使用指定的下游收集器(通常用作下游收集器本身,比如用于 groupingBy)进行处理。 joining() 假设元素为 String 类型,将这些元素联结到一个字符串中(或许使用分隔符、前缀和后缀)。 counting() 计算元素数量。(通常用作下游收集器。)
这里主要介绍一下Collectors.groupingBy(),它主要用于对元素的分组操作,同时它也可以通过结合下游收集器实现对元素更加复杂,更加细致的分组操作
1、groupingBy(Function<T,U>) 将元素分组到一个 Map 中,其中的键是所提供的应用于流元素的函数,值是共享该键的元素列表。
使用Student.age作为Map的key,value则是List<Student>
Map<Integer,List<Student>> map=list.stream().collect(Collectors.groupingBy(Student::getAge));
2、groupingBy(Function<T,U>, Collector) 将元素分组,使用指定的下游收集器来处理与每个组有关联的值。
将Student.age作为Map的key,只保留各年龄段身高最高的student
Map<Integer,Optional<Student>> map=list.stream().collect(Collectors.groupingBy(Student::getAge,Collectors.maxBy(Comparator.comparing(Student::getHeight))));
对于顺序执行,Stream构造了一个 Consumer对象链,其结构与管道结构相符。其中每个 Consumer 对象知道下一个阶段;当它收到一个元素(或被告知没有更多元素)时,它会将 0 或多个元素发送到链中的下一个阶段。例如,与 filter() 阶段有关联的 Consumer 将过滤器谓词应用于输入元素,并将它发送或不发送到下一个阶段;与 map() 阶段有关联的 Consumer 将映射函数应用于输入元素,并将结果发送到下一个阶段。与有状态操作(比如 sorted())有关联的 Consumer 会缓冲元素,直到它看到输入的末尾,然后将排序的数据发送到下一个阶段。Consumer对象链的最后一个阶段将实现终止操作。