当前位置: 首页 > 科技观察

stream

时间:2023-03-20 11:49:52 科技观察

的实用方法和注意事项filter、map、skip等用到但不常用的方法想必大家都不陌生,就不再赘述了。这里只介绍在工程中使用较少但同样实用的方法。?reducereduce有3个参数:initialvalue,accumulator,combiner。下面就几个案例一一为大家讲解。由于比较,下面贴出ide执行结果。当顺序读取流或累加器的参数匹配其实现类型时,我们不需要使用组合器。通常只有在处理对象属性时才需要组合器来帮助编译器推断输入参数类型。实际上,combiner并不真正在串行流中执行,只是输入输出参数类型符合编译器推理的要求。可以看到上面result3的计算,无论是对endcombiner应用max还是min,结果都是一样的。?allMatch/anyMatch/noneMatch判断某条规则是否集合中所有匹配/任意匹配存在/不存在匹配。比如下面这段代码判断集合中的所有对象是否合法。语义非常简单。下面对比流式写法和常规写法。两种写法的结果是一样的。@Data@AllArgsConstructorpublicstaticclassCalendar{privateLocalDate日期;今天的私人布尔值;privatebooleansigned;}//日期初始化LocalDatenow=newLocalDate();Listcalendars=Arrays.asList(newCalendar(newLocalDate(1661174238000L),false,false),newCalendar(newLocalDate(1661828371000L),假,假),新日历(新本地日期(1661433438000L),假,假),新日历(新本地日期(1661519838000L),假,假),新日历(新本地日期(1661779038000L),假,假),新日历(now,true,true));//判断昨天是否签到过。写法:booleanyesterdaySigned=calendars.stream().anyMatch(t->Days.daysBetween(t.getDate(),now).getDays()==1&&t.isSigned());System.out.println("Didyousigninyesterday->"+yesterdaySigned);//写2booleanyesterdaySigned2=false;for(Calendarcalendar:calendars){if(Days.daysBetween(calendar.getDate(),now).getDays()==1){//找到昨天的日历,判断是否签到yesterdaySigned2=calendar.isSigned();休息;}}System.out.println("昨天是否签到,写方法2->"+yesterdaySigned2);这里,写法1比较简洁但是有一个问题,你见过吗?这个问题在“注意事项”中有具体说明。?flatMap和map的区别在于可以将一个对象转换为多个对象并以流的形式返回,适用于集合嵌套场景下的扁平化处理。概念有点啰嗦,下面是附ide截图的演示。可见flatmap在某些场景下比map有先天的优势。注意事项?写顺序影响性能在stream的实际使用中,filter和map是最常见的。这两个操作是逐个元素进行的,一个一个传递给下游操作,我们称之为“垂直操作”(补充:sorted是一种“水平操作”,即会截断后续操作,直到流中所有元素自行操作)。其中,过滤器比较特殊,被它拦截后不会继续向下游传递。基于这个原则,将过滤器尽可能放在前面,往往可以大大提高流操作的性能。如下图:对于长度为5的字符集,如果顺序执行map-filter-foreach,会有5个map,5个filter,1个foreach;如果filter-map-foreach顺序执行,就会有5个filter,1个1个map,1个foreach执行。并且不难推断,滤波器的过滤度越高,性能差异就会越明显。很多人可能会觉得原理简单易懂,但遗憾的是,在大型项目中总能找到这种性能缺陷的代码,比如ListawardId=timeFilterAwardConfigs.stream().map(config->config.getAwardId()).filter(awardId->awardId>0).collect(Collectors.toList());但在更复杂的场景下,并不要求filter在进行其他操作之前无脑。例如下面的例子//假设一个用户集合ListuserList=Arrays.asList(newUser("张三",22),newUser("李四",21),newUser("王五",19),newUser("赵六",25));//输出这个集合中所有用户所在公司的年营业额总和,要求公司所在地在杭州市余杭区//注意用户可能有流浪汉。不考虑员工工作的公司重叠或一个人在多家公司工作的情况。//写1intallCompanyTurnover1=userList.stream().map(user->calculateAnnualTurnover(queryUserCompany(user))).filter(Objects::nonNull).reduce(0,Integer::sum);//写2intallCompanyTurnover2=userList.stream().filter(user->{Companycompany=queryUserCompany(user);returncompany!=null&&!"Yuhang".equals(company.getLocal());}).地图(用户->计算年营业额(queryUserCompany(用户))).reduce(0,整数::总和);写法1显然更直观,而写法2提前过滤掉一些数据,但是queryUserCompany有双重计算。因此,在这种情况下,需要平衡过滤器的过滤程度和queryUserCompany重复计算的开销。如果filter的过滤度足够高(比如余杭的公司很少),queryUserCompany的资源开销不大,那么第二种方法比较好,否则第一种方法比较好。?不适用所有场景。在性能方面,我们可以回到刚才讲anyMatch的时候看到的代码。//判断昨天是否签到。写法:booleanyesterdaySigned=calendars.stream().anyMatch(t->Days.daysBetween(t.getDate(),now).getDays()==1&&t.isSigned());System.out.println("Didyousigninyesterday->"+yesterdaySigned);//写2booleanyesterdaySigned2=false;for(Calendarcalendar:calendars){if(Days.daysBetween(calendar.getDate(),now).getDays()==1){//找到昨天的日历,在yesterdaySigned2=calendar.isSigned()中判断是否签到;休息;}}System.out.println("昨天是否签到,写法2->"+yesterdaySigned2);print观察执行次数如下很明显anyMatch会无条件遍历所有元素然后返回,但是直观的遍历写法往往不会犯这种错误,拿到结果后提前break即可。您可能会想到先使用过滤器获取“昨天”日历,然后使用anymatchbooleanyesterdaySigned=calendars.stream().filter(t->Days.daysBetween(t.getDate(),now).getDays()==1).anyMatch(日历::isSigned);不幸的是,过滤器也会完全遍历整个集合。其实看看所有的stream方法,好像也没有很好的办法解决这个问题。也欢迎大家一起讨论。可读性提取判断某个业务周期签到次数的方法,使用stream和for循环常规写法privateintgetCycleActionCount(Datestart,Dateend,Listcalendar){intcount=0;对于(ActionCalendarcalendarDay:calendar){Datedate=calendarDay.getDate();if(date.after(start)&&date.before(end)&&calendarDay.isComplete()){//周期内任意一天签到,签到次数自动增加。计数++;}}返回计数;}privateintgetCycleActionCount2(Datestart,Dateend,Listcalendar){returnMath.toIntExact(calendar.stream().filter(//统计周期t的签到天数->(t.getDate().after(start)&&t.getDate().before(end)&&t.isComplete())).count());}两者在可读性方面没有特别大的差异化程度。甚至熟练的流爱好者都认为,在写完一段流代码后,他们会多看几眼,以确认性能和缩进是否最佳。可以看出,在某些场景下,性能、可读性、书写便利性并不占优势。这个时候stream似乎并不是最好的选择。总结Stream在大部分场景下可以帮助我们更快的写出漂亮的代码,但是在更复杂的场景下,需要检查API之间的执行顺序,lambda表达式的使用,甚至这个场景是否适合用stream写做一些思考避免性能或可读性缺陷。总的来说,stream和intuitive对于遍历是互补的而不是替代的。两者的结合使工作变得容易。另外,stream家族还有一个强大的种子播放器“parallelStream”(并行流)没有介绍。通常用于非常大的集合的处理,在日常工程中很难找到使用场景。同时,在使用上也比上面提到的串行流处理多了一些注意事项。这里没有分享。