Spring WebFlux 学习笔记 - (一) 前传:学习Java 8 Stream Api (3) - Stream的终端操作
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)。
我们将终端操作的结果分为如下几类:
- 匹配
- 统计
- 消费
- 转换
以下内容提到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的使用3和reduce的使用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的终端操作的更多相关文章
- Spring WebFlux 学习笔记 - (一) 前传:学习Java 8 Stream Api (1) - 创建 Stream
影子 在学习Spring WebFlux之前,我们先来了解JDK的Stream,虽然他们之间没有直接的关系,有趣的是 Spring Web Flux 基于 Reactive Stream,他们中都带了 ...
- Spring WebFlux 学习笔记 - (一) 前传:学习Java 8 Stream Api (2) - Stream的中间操作
Stream API Java8中有两大最为重要的改变:第一个是 Lambda 表达式:另外一个则是 Stream API(java.util.stream.*). Stream 是 Java8 中处 ...
- 【SpringSecurity系列3】基于Spring Webflux集成SpringSecurity实现前后端分离无状态Rest API的权限控制
源码传送门: https://github.com/ningzuoxin/zxning-springsecurity-demos/tree/master/02-springsecurity-state ...
- Vue学习笔记-Vue.js-2.X 学习(一)===>基本知识学习
一 使用环境: windows 7 64位操作系统 二 IDE:VSCode/PyCharm 三 Vue.js官网: https://cn.vuejs.org/ 四 下载安装引用 方式1:直接 ...
- [原创]java WEB学习笔记75:Struts2 学习之路-- 总结 和 目录
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...
- [原创]java WEB学习笔记66:Struts2 学习之路--Struts的CRUD操作( 查看 / 删除/ 添加) 使用 paramsPrepareParamsStack 重构代码 ,PrepareInterceptor拦截器,paramsPrepareParamsStack 拦截器栈
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...
- 【神经网络与深度学习】学习笔记:AlexNet&Imagenet学习笔记
学习笔记:AlexNet&Imagenet学习笔记 ImageNet(http://www.image-net.org)是李菲菲组的图像库,和WordNet 可以结合使用 (毕业于Caltec ...
- Vue学习笔记-Vue.js-2.X 学习(三)===>组件化高级
(四) 组件化高级 1.插槽(slot)的基本使用 A:基本使用: <slot></slot> B:默认置:<slot><h1>中间可以放默认值< ...
- Vue学习笔记-Vue.js-2.X 学习(二)===>组件化开发
===重点重点开始 ========================== (三) 组件化开发 1.创建组件构造器: Vue.extends() 2.注册组件: Vue.component() 3.使用 ...
随机推荐
- React使用hook
Hook 是 React 16.8 的新增特性.它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性. 为什么会有hook 在组件之间复用状态逻辑很难,需要重新组织你 ...
- scrapy实现数据持久化、数据库连接、图片文件下载及settings.py配置
数据持久化的两种方式:(1)基于终端指令的持久化存储:(2)基于管道的持久化存储 基于终端指令的持久化存储 在爬虫文件的parse方法中必须要return可迭代对象类型(通常为列表或字典等)的返回值, ...
- Django路由配置之正则表达式详解
正则表达式详解 urls.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^articles ...
- 你想了解的python基础数据类型这里都有
目录 python基础数据总结 数字型数据类型 数字型数据基本知识 算术运算符 进制 二进制运算符 字符串数据类型 字符串基础知识 字符串数据操作方法(增 查 改) 集合数据类型 集合基础知识 集合元 ...
- 最小比率树 poj2728
以下内容均为转载 http://www.cnblogs.com/ftae/p/6947497.html poj2728(最小比率生成树) poj2728 题意 给出 n 个点的坐标和它的高度,求一 ...
- 【Redis】List常见应用场景
常用数据结构 Stack(栈) = LPUSH + LPOP ->FILO Queue(队列) = LPUSH + RPOP Blocking MQ(阻塞队列) = LPUSH + BRPOP ...
- 模板:list列表显示
作为视图,担当的角色就是显示数据.所以关键就是,借助JSTL的c:forEach标签遍历从CategoryServlet的list()的request.setAttribute("thecs ...
- SSE系列内置函数中的shuffle函数
SSE 系列内置函数中的 shuffle 函数 邮箱: quarrying@qq.com 博客: http://www.cnblogs.com/quarryman/ 发布时间: 2017年04月18日 ...
- SQL——模糊查询LIKE
LIKE操作符 - 用于WHERE子句中搜索列中指定模式. 语法:SELECT columnName(s) FROM tableName WHERE columnName LIKE patter ...
- PhpStorm2016.3激活
选择License server,输入以下任意一个地址: http://idea.imsxm.com/http://114.215.133.70:41017/http://mcpmcc.com:101 ...