转载

JAVA Stream简单介绍

Stream库

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();

可以发现,使用Stream进行计算的代码更加简洁,并且可读性更强

流的关注点不是数据,而是计算,流具有很强的数据聚合、汇总能力。一个流管道包含一个流来源、0 或多个中间操作,以及一个终止操作。

使用流进行数据计算可以大致分为三个步骤:

1、由数据源构建流管道,即创建一个Stream

2、执行中间流操作

3、执行终止流操作

  • 中间流操作负责将一个流转换为另一个流,中间流操作是惰性的:它只会设置流管道的下一个阶段,并不会启动任何操作。对数据的处理在执行终止操作时开始,执行终止操作之后,会终止当前流管道,该流管道不能再被使用,如果需要遍历同一个数据集,需要重新设置一个流管道。
  • 中间操作可进一步划分为无状态(filter()、map()、flatMap())和有状态(sorted()、limit()、distinct())操作。无状态操作是可在元素上直接执行而无需知道其他任何元素的操作。例如,过滤操作只需检查当前元素来确定是包含还是消除它;有状态操作则表现为对当前元素的操作会受到其他元素的影响,如排序操作必须查看所有元素之后才能确定当前元素的位置顺序

构建流管道:

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()可以将元素序列聚合到该类型的单个元素值。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.collect()方法

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的原理

对于顺序执行,Stream构造了一个 Consumer对象链,其结构与管道结构相符。其中每个 Consumer 对象知道下一个阶段;当它收到一个元素(或被告知没有更多元素)时,它会将 0 或多个元素发送到链中的下一个阶段。例如,与 filter() 阶段有关联的 Consumer 将过滤器谓词应用于输入元素,并将它发送或不发送到下一个阶段;与 map() 阶段有关联的 Consumer 将映射函数应用于输入元素,并将结果发送到下一个阶段。与有状态操作(比如 sorted())有关联的 Consumer 会缓冲元素,直到它看到输入的末尾,然后将排序的数据发送到下一个阶段。Consumer对象链的最后一个阶段将实现终止操作。

原文  https://segmentfault.com/a/1190000020528435
正文到此结束
Loading...