WebFlux前置知识(三)
Stream流式API也是JDK8开始引入的API,用过的小伙伴都说爽,没用过的小伙伴则感到一脸懵逼。现在JDK已经到16了,可能很多人还不熟悉Stream流操作,这次趁着讲WebFlux,和大家一起回顾一下Stream流操作。1。Stream流简介
说到Stream,大家可能很容易想到JavaIO中的各种流操作,名字看起来很像,但这其实是两个完全不同的东西。
Java8中的Stream不存储数据,它通过函数式编程模式来对集合进行链状流式操作。2。基本玩法
先来个简单的例子感受下Stream的操作。
例如我有一个数组,如下:int〔〕arr{1,2,3,4,5,6};
现在想给这个数组求平均数,如下:doubleasDoubleIntStream。of(arr)。average()。getAsDouble();
一行代码搞定,首先调用IntStream。of方法获取流对象,然后调用average方法计算平均数,再调用getAsDouble方法获取结果。全程都是方法调用,不用自己写求平均数的逻辑。
再比如求和,如下:intsumIntStream。of(arr)。sum();
也是直接调用sum方法即可。
而且我们还可以在流中对数据进行二次加工,例如给数组中的每个元素先求平方再求和:double〔〕arr{1,2,3,4,5,6};doublesumDoubleStream。of(arr)。map(iMath。pow(i,2))。sum();
先在Map中对每一个元素求平方,然后再求和。
这里就能涉及到两个概念:中间操作终止操作
所谓中间操作,就是中途求平方的操作,所谓终止操作就是最终计算出结果的操作,只要方法的返回值不是一个Stream,那就是终止操作,否则就是中间操作。3。Stream的创建
获取一个流的方式多种多样,最常见的就是从集合或者数组中获取一个流,当然我们也可以直接自己创建一个流出来。
一起来看下。集合
List集合或者Set集合都可以直接搞一个流出来,方式如下:ListStringlistnewArrayList();StreamStrings1list。stream();SetStringsetnewHashSet();StreamStrings2set。stream();
集合中直接调用stream方法就可以获取到流。数组
通过数组获取流的方式也很简单,如下:IntStreamstreamArrays。stream(newint〔〕{11,22,33,44,55,66});数字Stream
也可以直接利用IntStream、LongStream等对象创建一个数字Stream,如下:IntStreams1IntStream。of(1,2,3);DoubleStreams2DoubleStream。of(1,2,3);LongStreams3LongStream。of(1L,2L,3L);自己创建RandomrandomnewRandom();SupplierIntegersupplier()random。nextInt(100);StreamIntegerstreamStream。generate(supplier)。limit(5);
调用Stream。generate方法可以自己创建一个流,自己创建的时候需要提供一个Supplier,通过调用Supplier中的get方法自动获取到元素。
无论哪种创建方式,大家需要明白的是,Stream并不会保存数据,它只会对数据进行加工。4。Stream的中间操作
中间操作可以分为两大类:map或者filter会从输入流中获取每一个元素,并且在输出流中得到一个结果,这些操作没有内部状态,称为无状态操作。reduce、sum、max这些操作都需要内部状态来累计计算结果,所以称为有状态操作。
分别来看下:
无状态操作mapmapToXxxflatMapflatMapToXxxfilterpeek
有状态操作distinctsortedlimitskip
map
Stream。map()是Stream中最常用的一个转换方法,可以把一个Stream对象转为另外一个Stream对象。map方法所接收的参数就是一个Function对象,松哥在前面文章中和大家介绍过Function对象了,就是有输入有输出(参见WebFlux前置知识(一)),了解了map的参数,那么map的功能就很明白了,就是对数据进行二次加工。
举个栗子,例如把一个字符串数组转为数字:String〔〕arr{1,2,3};StreamStrings1Arrays。stream(arr);StreamIntegers2s1。map(iInteger。valueOf(i));
再比如一个数字流,给所有的元素乘2,如下:IntStream。of(1,2,3)。map(i2i)。forEach(System。out::println);
最后的forEach就是将元素打印出来。
JDK中也提供了一些现成的格式转换,如下图:
这样可以直接将元素转为Double、Long、Obj等类型,例如下面这样:String〔〕arr{1,2,3};StreamStrings1Arrays。stream(arr);s1。mapToLong(iLong。parseLong(i))。forEach(System。out::println);
flatMap
flatMap可以把Stream中的每个元素都映射为一个Stream,然后再把这多个Stream合并为一个Stream。
例如如下代码,返回的Stream中的元素是数组:StreamInteger〔〕sStream。of(newInteger〔〕{1,2,3},newInteger〔〕{4,5,6},newInteger〔〕{7,8,9});s。forEach(System。out::println);
通过flatMap我们可以将Stream中的元素变为Integer:StreamIntegersStream。of(newInteger〔〕{1,2,3},newInteger〔〕{4,5,6},newInteger〔〕{7,8,9})。flatMap(iArrays。stream(i));s。forEach(System。out::println);
filter
filter操作会对一个Stream中的所有元素一一进行判断,不满足条件的就被过滤掉了,剩下的满足条件的元素就构成了一个新的Stream。
例如要找到数组中所有大于3的元素,如下:IntStream。of(2,3,4,5,6,7)。filter(ii3)。forEach(System。out::println);
filter方法接收的参数是Predicate接口函数,关于Predicate接口函数,大家可以参考WebFlux前置知识(一))一文。
peek
peek的入参是Consumer,没有返回值,因此当我们要对元素内部进行处理时,使用peek是比较合适的,这个时候可以不用map(map的入参是Function,它是有返回值的)。peek方法本身会继续返回流,可以对数据继续进行处理。
举个简单的数据转换的例子吧(最终返回的数据并不会被转换):IntStream。of(2,3,4,5,6,7)。filter(ii3)。peek(String::valueOf)。forEach(iSystem。out。println(i));
peek方法的感觉就像数据中途被消费了一次。
distinct
这个是去重。由于去重操作需要获取到其他元素的值(比较之后才知道是否重复),所以这个是有状态操作。如下:IntStream。of(2,3,4,3,7,6,2,5,6,7)。distinct()。forEach(System。out::println);
sorted
sorted是排序,因为也需要知道其他元素的值,然后才能去重,所以这个也是有状态操作,如下:IntStream。of(2,3,4,3,7,6,2,5,6,7)。distinct()。sorted()。forEach(System。out::println);
limitskip
limit和skip配合操作有点像数据库中的分页,skip表示跳过n个元素,limit表示取出n个元素。例如下面这个例子:Arrays。asList(A,B,C,D,E,F)。stream()。skip(2)。limit(3)。forEach(System。out::println);
这个会跳过A和B,最终打印出CDE。这也是一种有状态操作。5。Stream终止操作
终止操作就是最终计算出结果的操作,只要方法的返回值不是一个Stream,那就是终止操作,否则就是中间操作。
终止操作又分为两类:短路操作:不用处理全部元素就可以返回结果。非短路操作:必须处理所有元素才能得到最终结果。
各自都包含哪些操作,我们分别来看下:
非短路操作forEachforEachOrderedcollecttoArrayreduceminmaxcount
短路操作findFirstfindAnyallMatchanyMatchnoneMatch
forEachforEachOrdered
forEach和forEachOrdered都是接收一个Consumer类型的参数,完成对参数的消费,不同的是,在并行流中,forEachOrdered会保证执行顺序。
例如如下一段代码:int〔〕arr{1,2,3,4,5,6,7,8,9};Arrays。stream(arr)。parallel()。forEach(System。out::println);Arrays。stream(arr)。parallel()。forEachOrdered(System。out::println);
前者打印出来的顺序不一定是123456789,后者一定是。
collecttoArray
这两个都是收集器,可以将执行结果转为一个List集合或者一个数组:ListIntegerlistStream。of(1,2,3,4)。filter(pp2)。collect(Collectors。toList());System。out。println(list);
reduce
reduce是Stream的一个聚合方法,它可以把一个Stream的所有元素按照聚合函数聚合成一个结果。reduce方法传入的对象是BinaryOperator接口,它定义了一个apply方法,负责把上次累加的结果和本次的元素进行运算,并返回累加的结果。
举个简单的例子,数组求和,当然可以直接调用sum计算,我们这里也可以调用reduce来实现,如下:OptionalIntegeroptionalStream。of(1,2,3,4)。reduce((i,j)ij);System。out。println(optional。orElse(1));
reduce的参数是BinaryOperator,这个接收两个参数,第一个参数是之前计算的结果,第二个参数是本次参与计算的元素,两者累加求和。
再比如给一段话中间加上。,方式如下:OptionalStringsStream。of(wwwjavaboyorg。split())。reduce((i,j)i。j);System。out。println(s。orElse());
最终执行结果如下:w。w。w。j。a。v。a。b。o。y。o。r。g
minmaxcount
这个就比较简单了,就是求最大值最小值,统计总个数,如下表示统计总个数:StreamIntegersStream。of(1,2,3,4);longcounts。count();System。out。println(countcount);
如下表示统计最小值:StreamIntegersStream。of(1,2,3,4);OptionalIntegermins。min(Comparator。comparingInt(ii));System。out。println(min。get()min。get());
findFirstfindAny
这两个就是返回流中的第一个、任意一个元素,findAny要在并行流中测试才有效果,举个栗子:for(inti0;i10;i){OptionalIntegerfirstStream。of(1,2,3,4)。parallel()。findFirst();System。out。println(first。get()first。get());}System。out。println();for(inti0;i10;i){OptionalIntegerfirstStream。of(1,2,3,4)。parallel()。findAny();System。out。println(first。get()first。get());}
allMatchanyMatchnoneMatch
allMatch、anyMatch、noneMatch用来判断所有元素、任意元素或者没有元素满足给定的条件。这三个方法的参数都是一个Predicate接口函数。booleanbStream。of(1,2,3,4)。allMatch(ii5);System。out。println(bb);6。并行流
通常情况下,对Stream的元素进行处理是单线程的,即一个一个元素进行处理。有时候我们希望可以并行处理Stream元素,因为在元素数量非常大的情况,并行处理可以大大加快处理速度。
把一个普通Stream转换为可以并行处理的Stream非常简单,只需要用parallel方法进行转换:newRandom()。ints()。limit(50)。parallel()。forEach(i{System。out。println(Thread。currentThread()。getName()i);});
这样数据在后台就是并行打印的。7。收集器
收集器可以将计算结果重新整理收集到一个集合中,这个集合可以是一个ListSet获取其他,并且还可以在收集的过程中对数据进行处理。
例如我有一个users集合,里边保存了用户数据,用户有username、age以及gender三个属性,如下代码分别表示:提取出用户对象中的age属性组成新的集合并返回。提取出用户对象中的username属性组成新的集合并返回。提取出用户对象中的gender属性组成新的集合并返回(这里是一个Set集合,所以会自动去重)。ListIntegeragesusers。stream()。map(User::getAge)。collect(Collectors。toList());System。out。println(agesages);ListStringusernamesusers。stream()。map(User::getUsername)。collect(Collectors。toList());System。out。println(usernamesusernames);SetStringgendersusers。stream()。map(User::getGender)。collect(Collectors。toSet());System。out。println(gendersgenders);
Collectors。toList()最终返回的是ArrayList,Collectors。toSet()最终返回的是HashSet。
如果我们想返回一个Vector或者TreeSet,也是可以的,如下:ListIntegeragesusers。stream()。map(User::getAge)。collect(Collectors。toList());System。out。println(agesages);ListStringusernamesusers。stream()。map(User::getUsername)。collect(Collectors。toCollection(Vector::new));System。out。println(usernamesusernames);TreeSetStringgendersusers。stream()。map(User::getGender)。collect(Collectors。toCollection(TreeSet::new));System。out。println(gendersgenders);
也可以获取某一个字段的统计信息:IntSummaryStatisticsageStatisticsusers。stream()。collect(Collectors。summarizingInt(User::getAge));System。out。println(ageStatisticsageStatistics);
这个统计信息中包含:总和、最小值、平均值以及最大值等:ageStatisticsIntSummaryStatistics{count20,sum1222,min9,average61。100000,max96}
还可以对数据进行分块,将男女不同性别统计出来:MapBoolean,ListUsermapusers。stream()。collect(Collectors。partitioningBy(uu。getGender()。equals(男)));System。out。println(mapmap);
也可以按照性别对数据进行分组,如下:MapString,ListUsermap2users。stream()。collect(Collectors。groupingBy(User::getGender));System。out。println(map2map2);
分组后,Map中的key就是性别;分块后,Map中的key就是truefalse。
再比如统计男女的人数:MapString,Longmap2users。stream()。collect(Collectors。groupingBy(User::getGender,Collectors。counting()));System。out。println(map2map2);
好啦,今天我们就先聊这么多~
原文链接:https:mp。weixin。qq。comscuxnkcfAOLZuxWRmYcnKWg