Stream API

Java8中有两大最为重要的改变:第一个是 Lambda 表达式;另外一个则是 Stream API(java.util.stream.*)。

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

流(Stream)是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。"集合讲的是数据,流讲的是计算! "

集合和流(Stream),表面上有一些相似之处,他们有不同的目标。集合主要关注其元素的有效管理和访问,相比之下,流不提供直接访问或操纵元素的手段,而是关心声明性地描述其源和将在该源上进行聚合的计算操作。

上篇内容我们学习了Stream的中间操作,接下来我们来看下Stream数据流的结果消费,即终端(终止)操作。以下用 终端操作 统称。

Stream的终端操作

流管道通过源生成,经过零个或多个中间操作后,进行最后的终端操作,由此产生结果或副作用,如count()或forEach(Consumer)。

我们将终端操作的结果分为如下几类:

  1. 匹配
  2. 统计
  3. 消费
  4. 转换

以下内容提到XxxStream代表IntStream、LongStream、DoubleStream。如无特殊说明Stream也包含IntStream、LongStream、DoubleStream。

匹配

匹配类型的终端操作返回值为布尔值,根据使用语境调用不同的方法及传入谓语实现确定是否匹配。

序号 支持的类 方法定义 方法说明
1 Stream boolean anyMatch(Predicate<? super T> predicate); 部分匹配,返回此流的任何元素是否与提供的谓词匹配。
2 Stream boolean allMatch(Predicate<? super T> predicate); 全部匹配,返回此流的所有元素是否与提供的谓词匹配。
3 Stream boolean noneMatch(Predicate<? super T> predicate); 全不匹配,返回此流中是否没有元素与提供的谓词匹配。

注意:如果流为空时,allMatch方法始终返回true;noneMatch方法始终返回true。

以下代码见 StreamTerminalOperationMatchTest。

anyMatch的使用

// 是否存在匹配的元素,true
log.info("[1, 2, 3, 4, 5, 6]存在偶数否:{}",
Stream.of(1, 2, 3, 4, 5, 6).anyMatch(n -> n % 2 == 0));

allMatch的使用

// 全部元素是否都匹配,false
log.info("[1, 2, 3, 4, 5, 6]全部都是偶数否:{}",
Stream.of(1, 2, 3, 4, 5, 6).allMatch(n -> n % 2 == 0));

noneMatch的使用

// 全部元素是否都不匹配,false
log.info("[1, 2, 3, 4, 5, 6]全部都不是偶数否:{}",
Stream.of(1, 2, 3, 4, 5, 6).noneMatch(n -> n % 2 == 0));

空流验证

// 空流中不存在任何匹配元素,所以返回false
log.info("空流是否AnyMatch:{}", Stream.empty().anyMatch(Objects::isNull));
// 空流中不存在不匹配的,即全部匹配,所以返回true
log.info("空流是否AllMatch:{}", Stream.empty().allMatch(Objects::isNull));
// 空流中全部都不匹配,所以返回true
log.info("空流是否NoneMatch:{}", Stream.empty().noneMatch(Objects::isNull));

统计

统计类型的终端操作是对流元素的统计,如元素个数、最大值、最小值、统计对象等。

序号 支持的类 方法定义 方法说明
1 Stream long count(); 返回此流中的元素数。
2 Stream Optional min(Comparator<? super T> comparator); 根据提供的 Comparator返回此流的最小元素。
3 Stream Optional max(Comparator<? super T> comparator); 根据提供的 Comparator返回此流的最大元素。
4 Stream OptionalXxx min(); 返回 OptionalInt此流的最小元素的OptionalInt,如果此流为空,则返回一个空的可选项。
5 XxxStream OptionalXxx max(); 返回 OptionalInt此流的最大元素的OptionalInt,如果此流为空,则返回一个空的可选项。
6 XxxStream OptionalDouble average(); 返回 OptionalDouble此流的元素的算术平均值的OptionalDouble,如果此流为空,则返回空的可选项。
7 XxxStream Xxx sum(); 返回此流中元素的总和。
8 XxxStream XxxSummaryStatistics summaryStatistics(); 返回一个 IntSummaryStatistics描述有关此流的元素的各种摘要数据。

XxxSummaryStatistics类型的统计对象,如IntSummaryStatistics,除了提供最小值、最大值、平均值、元素个数、总和外,还提供了accept、combine两个方法,分别支持添加新的数据和连接另外的统计对象,并自动重新统计结果。

以下代码见 StreamTerminalOperationStatisticsTest。

count的使用

log.info("[1, 2, 3, 4, 5, 6]元素个数:{}",
Stream.of(1, 2, 3, 4, 5, 6).count());

min的使用(使用Comparator比较)

log.info("[1, 2, 3, 4, 5, 6]的最大值:{}",
Stream.of(1, 2, 3, 4, 5, 6).min(Comparator.comparingInt(n -> n)).get());

max的使用(使用Comparator比较)

log.info("[1, 2, 3, 4, 5, 6]的最小值:{}",
Stream.of(1, 2, 3, 4, 5, 6).max(Comparator.comparingInt(n -> n)).get());

min的使用

log.info("[1, 2, 3, 4, 5, 6]的最小值:{}",
IntStream.of(1, 2, 3, 4, 5, 6).min().getAsInt());

max的使用

log.info("[1, 2, 3, 4, 5, 6]的最大值:{}",
IntStream.of(1, 2, 3, 4, 5, 6).max().getAsInt());

average的使用

log.info("[1, 2, 3, 4, 5, 6]的平均值:{}",
IntStream.of(1, 2, 3, 4, 5, 6).average().getAsDouble());

sum的使用

log.info("[1, 2, 3, 4, 5, 6]的求和:{}",
IntStream.of(1, 2, 3, 4, 5, 6).sum());

summaryStatistics的使用

IntSummaryStatistics summaryStatistics = IntStream.of(1, 2, 3, 4, 5, 6).summaryStatistics();
log.info("[1, 2, 3, 4, 5, 6]的统计对象:{}", summaryStatistics);
summaryStatistics.accept(7);
log.info("添加7后,统计对象变为:{}", summaryStatistics);
IntSummaryStatistics summaryStatistics2 = IntStream.of(8, 9).summaryStatistics();
summaryStatistics.combine(summaryStatistics2);
log.info("合并[8, 9]后,统计对象变为:{}", summaryStatistics);

消费

消费类型的终端操作是对流内元素的获取或循环消费。

序号 支持的类 方法定义 方法说明
1 Stream Optional findFirst(); 返回描述此流的第一个元素的Optional,如果流为空,则返回一个空的Optional。
2 Stream Optional findAny(); 返回描述流的一些元素的Optional如果流为空,则返回一个空的Optional。
3 Stream void forEach(Consumer<? super T> action); 对此流的每个元素执行操作。
4 Stream void forEachOrdered(Consumer<? super T> action); 如果流具有定义的顺序,则以流的顺序对该流的每个元素执行操作。

看了forEach和forEachOrdered的Api的说明,大家可能对这两个还是有点疑问,这里特别说明下,forEach在并行流(parallel后面会讲)中并不按照流内元素之前定义的顺序执行操作,是无序的,而forEachOrdered会按照流之前定义的顺序执行操作。除非必要,在并行流中不建议使用forEachOrdered对其进行排序执行操作,否则影响性能。

流有可能也可能没有定义顺序。流是否有顺序取决于源和中间操作。某些流源(如List或数组)本质上是有序的,而其他数据源(如HashSet)不是。一些中间操作(例如sorted())可以在其他无序流上排序,而其他中间操作可以使有序流无序,例如BaseStream.unordered()。此外,一些终端操作可能会忽略顺序,如forEach()。

如果一个流被命令,大多数操作被限制为在遇到的顺序中对元素进行操作; 如果流的源是List含有[1, 2, 3] ,然后执行的结果map(x -> x*2)必须是[2, 4, 6] 。 然而,如果源没有定义的顺序,则任何[2, 4, 6]排列组合值都将是有效结果。

对于顺序流,遇到顺序的存在或不存在不影响性能,仅影响确定性。 如果流被排序,在相同的源上重复执行相同的流管线将产生相同的结果; 如果没有排序,重复执行可能会产生不同的结果。

对于并行流,放宽排序约束有时可以实现更有效的执行。如果元素的排序不相关,某些聚合操作,例如过滤重复(distinct())或组合减少(Collectors.groupingBy())可以更有效地实现。 类似地,本质上与遇到顺序相关的操作,如limit()可能需要缓冲以确保正确排序,从而破坏并行性的好处。另外,当流遇到排序,但用户并不特别在意那次偶遇秩序的情况下,明确地去操作为无序流unordered()可以提高某些状态或终端操作的并行性能。 然而,大多数流管线,例如上面的例子的“权重之和”,仍然在有序的限制下有效地并行化。

以下代码见 StreamTerminalOperationConsumeTest。

findFirst的使用

log.info("[1, 2, 3, 4, 5, 6]的首个值:{}",
Stream.of(1, 2, 3, 4, 5, 6).parallel().findFirst().get());

findAny的使用

log.info("[1, 2, 3, 4, 5, 6]的任意值:{}",
Stream.of(1, 2, 3, 4, 5, 6).parallel().findAny().get());

forEach的使用

// 并行后顺序随机,输出不保证顺序
log.info("[1, 2, 3, 4, 5, 6]的并行后循环输出:");
Stream.of(1, 2, 3, 4, 5, 6).parallel().forEach(System.out::println);

forEachOrdered的使用

// 无论是否并行,始终按照流定义的顺序或排序后的结果输出
log.info("[1, 2, 5, 6, 3, 4]的并行后顺序循环输出:");
Stream.of(1, 2, 5, 6, 3, 4).sorted().parallel().forEachOrdered(System.out::println);

转换

转换类型的终端操作是将流转换为另一种对象使用。

序号 支持的类 方法定义 方法说明
1 Stream Optional reduce(BinaryOperator accumulator); 使用associative累积函数对此流的元素执行reduction,并返回描述减小值(如果有的话)的Optional 。
2 Stream T reduce(T identity, BinaryOperator accumulator); 使用提供的身份值和 associative累积功能对此流的元素执行 reduction ,并返回减小的值。
3 Stream U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator combiner); 执行 reduction在此流中的元素,使用所提供的身份,积累和组合功能。
4 Stream <R, A> R collect(Collector<? super T, A, R> collector); 使用 Collector对此流的元素执行 mutable reduction Collector。
5 Stream R collect(Supplier supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); 对此流的元素执行 mutable reduction操作。

此处我们着重说下序号3,带有3个参数的reduce方法,该方法支持转换元素(结果)类型,即从类型T转换为类型U。第1个参数代表初始值;第2个参数是累加器函数式接口,输入类型U和类型T,返回类型U;第3个参数是组合器函数式接口,输入类型U和类型U,返回类型U。该方法的第3个参数在并行执行下有效。

同时需要注意,此方法有如下要求:

  • u = combiner(identity, u);
  • combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t);

该方法的代码示例见reduce的使用3reduce的使用4,如果对该示例不了解,可以在后面的章节中讲解了并行执行之后再回过头来看该示例。

以下代码见 StreamTerminalOperationTransformTest。

reduce的使用1

// 使用reduce方式实现查找最小值
log.info("[1, 2, 3, 4, 5, 6]的最小值:{}",
Stream.of(1, 2, 3, 4, 5, 6).reduce(Integer::min).get());

reduce的使用2

// 使用reduce方式实现求和
log.info("[1, 2, 3, 4, 5, 6]的求和:{}",
Stream.of(1, 2, 3, 4, 5, 6).reduce(0, Integer::sum));

reduce的使用3

// 求单词长度之和
Integer lengthSum = Stream.of("I", "love", "you", "too")
.parallel()
.reduce(0,// 初始值 // (1)
(sum, str) -> sum + str.length(), // 累加器 // (2)
Integer::sum);// 部分和拼接器,并行执行时才会用到 // (3)
// int lengthSum = stream.mapToInt(str -> str.length()).sum();
log.info("ILoveYouToo的长度为:{}", lengthSum);

reduce的使用4

// 下方方法同步执行时,能出现正确结果
// 并行执行时,将出现意想不到的结果
// 多线程执行时,append导致初始值identity发生了变化,而多线程又导致了数据重复添加
StringBuffer word = Stream.of("I", "love", "you", "too")
.parallel() // 同步执行注释该步骤
.reduce(new StringBuffer(),// 初始值 // (1)
StringBuffer::append, // 累加器 // (2)
StringBuffer::append);// 部分和组合器,并行执行时才会用到 // (3)
log.info("拼接字符串为:{}", word); // 此处如果使用字符串concat,导致性能降低,不停创建字符串常量
String word2 = Stream.of("I", "love", "you", "too")
.parallel() // 同步执行注释该步骤
.reduce("",// 初始值 // (1)
String::concat, // 累加器 // (2)
String::concat);// 部分和组合器,并行执行时才会用到 // (3)
log.info("拼接字符串为:{}", word2); // 下面方法并行执行时,虽然能达到正确的结果,但是并未满足reduce的要求
List<Integer> accResult = Stream.of(1, 2, 3, 4)
.parallel()
.reduce(Collections.synchronizedList(new ArrayList<>()),
(acc, item) -> {
List<Integer> list = new ArrayList<>();
list.add(item);
System.out.println("item BiFunction : " + item);
System.out.println("acc+ BiFunction: " + list);
return list;
}, (accs, items) -> {
accs.addAll(items);
System.out.println("item BinaryOperator: " + items);
System.out.println("acc+ BinaryOperator: " + accs);
return accs;
});
log.info("accResult: {}", accResult);

由于时间及版面的缘故,本期就先讲到这里,下期在着重将collect。

源码详见:https://github.com/crystalxmumu/spring-web-flux-study-note

以上是本期笔记的内容,我们下期见。

Spring WebFlux 学习笔记 - (一) 前传:学习Java 8 Stream Api (3) - Stream的终端操作的更多相关文章

  1. Spring WebFlux 学习笔记 - (一) 前传:学习Java 8 Stream Api (1) - 创建 Stream

    影子 在学习Spring WebFlux之前,我们先来了解JDK的Stream,虽然他们之间没有直接的关系,有趣的是 Spring Web Flux 基于 Reactive Stream,他们中都带了 ...

  2. Spring WebFlux 学习笔记 - (一) 前传:学习Java 8 Stream Api (2) - Stream的中间操作

    Stream API Java8中有两大最为重要的改变:第一个是 Lambda 表达式:另外一个则是 Stream API(java.util.stream.*). Stream 是 Java8 中处 ...

  3. 【SpringSecurity系列3】基于Spring Webflux集成SpringSecurity实现前后端分离无状态Rest API的权限控制

    源码传送门: https://github.com/ningzuoxin/zxning-springsecurity-demos/tree/master/02-springsecurity-state ...

  4. Vue学习笔记-Vue.js-2.X 学习(一)===>基本知识学习

    一  使用环境: windows 7 64位操作系统 二  IDE:VSCode/PyCharm 三  Vue.js官网: https://cn.vuejs.org/ 四  下载安装引用 方式1:直接 ...

  5. [原创]java WEB学习笔记75:Struts2 学习之路-- 总结 和 目录

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  6. [原创]java WEB学习笔记66:Struts2 学习之路--Struts的CRUD操作( 查看 / 删除/ 添加) 使用 paramsPrepareParamsStack 重构代码 ,PrepareInterceptor拦截器,paramsPrepareParamsStack 拦截器栈

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  7. 【神经网络与深度学习】学习笔记:AlexNet&Imagenet学习笔记

    学习笔记:AlexNet&Imagenet学习笔记 ImageNet(http://www.image-net.org)是李菲菲组的图像库,和WordNet 可以结合使用 (毕业于Caltec ...

  8. Vue学习笔记-Vue.js-2.X 学习(三)===>组件化高级

    (四) 组件化高级 1.插槽(slot)的基本使用 A:基本使用: <slot></slot> B:默认置:<slot><h1>中间可以放默认值< ...

  9. Vue学习笔记-Vue.js-2.X 学习(二)===>组件化开发

    ===重点重点开始 ========================== (三) 组件化开发 1.创建组件构造器: Vue.extends() 2.注册组件: Vue.component() 3.使用 ...

随机推荐

  1. IndexDB

    参考:http://www.ruanyifeng.com/blog/2018/07/indexeddb.html   cookie localStorage IndexedDB 大小 不超过4KB 2 ...

  2. 【遗传编程/基因规划】Genetic Programming

    目录 背景介绍 程序表示 初始化 (Initialization) Depth定义 Grow方法 Full方法 Ramped half-and-half方法 适应度(Fitness)与选择(Selec ...

  3. nginx配置之负载均衡

    nginx负载均衡 nginx代理机分发到多台同一项目的服务机 负载均衡器代理机配置:nginx.conf的http{}: #代理池,运行在不同服务机的程序 upstream loadtest{ se ...

  4. Spring 中的事件处理

    Spring 中的事件处理 Spring 的核心是 ApplicationContext,它负责管理 beans 的完整生命周期.当加载 beans 时,ApplicationContext 发布某些 ...

  5. Poj 3013基础最短路

    Description Christmas is coming to KCM city. Suby the loyal civilian in KCM city is preparing a big ...

  6. Liquibase使用小结

    简介 Liquibase是一个用于跟踪.管理和应用数据库变化的开源数据库重构工具.它将所有数据库的变化保存在XML文件中,便于版本控制和项目部署升级.在快速搭建项目的JHipster框架中集成了该工具 ...

  7. 封装 private

    封装表现: 1.方法就是一个最基本封装体. 2.类其实也是一个封装体. 从以上两点得出结论,封装的好处: 1.提高了代码的复用性. 2.隐藏了实现细节,还要对外提供可以访问的方式.便于调用者的使用.这 ...

  8. 剑指Offer之二进制中1的个数

    题目描述 输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示.     解法1:使用Integer.toBinanryString()返回int变量的二进制表示的字符串. [在Intege ...

  9. 移动端适配-rem(新)

    概念 对于移动端开发来说,无可避免的就是直面各种设备不同分辨率和不同DPR(设备像素比)的问题,在此忽略其他兼容性问题的探讨. 移动端像素 设备像素(dp),也叫物理像素.指设备能控制显示的最小物理单 ...

  10. Spring boot Sample 004之spring-boot-configuration-yaml

    一.环境 1.1.Idea 2020.1 1.2.JDK 1.8 二.目的 通过yaml文件配置spring boot 属性文件 三.步骤 3.1.点击File -> New Project - ...