当前位置: 首页 > 后端技术 > Java

高级Java开发工程师-Java8Stream使用技巧

时间:2023-04-01 17:30:09 Java

作者:Magic来源:恒生轻云社区什么是stream?Stream是Java8引入的一个全新的概念。它用于处理集合中的数据。暂时可以理解为高级合集。众所周知,采集操作非常繁琐。要过滤和投影集合,您需要编写大量代码。Streams以声明的形式操作集合,就像SQL语句一样。我们只需要告诉流需要对集合执行哪些操作。它会自动运行,把执行结果交给你,不需要我们自己写代码。因此,流的收集操作对我们来说是透明的。我们只需要向流发出命令,它就会自动给我们想要的结果。由于操作过程完全由Java处理,它可以根据当前的硬件环境选择最优的方法,我们不需要编写复杂且容易出错的多线程代码。流的特性只能遍历一次。我们可以把流想象成一个管道。管道的来源是我们的数据源(一个集合)。数据源中的元素依次发送到管道。我们可以对管道上的元素执行各种操作。种操作。一旦元素到达管道的另一端,它们就会被“消耗掉”,我们无法再对流进行操作。当然,我们可以从数据源中获取一个新的流,重新遍历一遍。如果我们使用内部迭代的方式来处理集合,我们需要手工编写处理代码,这就是外部迭代。而对流进行处理,我们只需要告诉流我们需要什么结果,处理过程就由流自己来完成,这就是所谓的内部迭代。流操作的类型流操作有两种类型:中间操作和终端操作。中间操作当数据源中的数据被放到管道上时,这个过程中对数据的所有操作都称为“中间操作”。中间操作仍然返回一个流对象,所以多个中间操作可以链接在一起形成一个管道。终端操作当所有中间操作完成后,如果要将数据从管道中取出,需要进行终端操作。终端操作会返回一个执行结果,就是你要的数据。流的操作过程使用流总共需要三个步骤:准备一个数据源来执行中间操作。可以有多个中间操作,它们可以串联起来形成管道。执行终端操作执行终端操作后,流结束,您将获得执行结果。流的使用在创建流之前,首先需要有一个数据源,通过StreamAPI提供的一些方法获取数据源的流对象。数据源可以有多种形式:1、collection的数据源比较常用,可以通过stream()方法获取stream对象:Listlist=newArrayList();Streamstream=list.stream();2.Array通过Arrays类提供的静态函数stream()获取数组的流对象:String[]names={"chaimm","peter","john"};Streamstream=Arrays.stream(名称);3、value直接把几个值变成一个stream对象:Streamstream=Stream.of("chaimm","peter","john");4.Filetry(Streamlines=Files.lines(Paths.get("filepathname"),Charset.defaultCharset())){//TODO}catch(IOExceptione){}5.迭代器创建无限流Stream.iterate(0,n->n+2).limit(10).forEach(System.out::println);Java7简化了IO操作,关闭IO的代码可以省略,把打开IO操作放在try后面的括号里。Filter过滤器函数接收一个Lambda表达式作为参数,该表达式返回布尔值。在执行过程中,流将元素一个一个地送到过滤器中,过滤掉执行结果为真的元素。例如,要过滤掉所有学生:Listresult=list.stream().filter(Man::isStudent).collect(toList());删除重复结果:Listresult=list.stream().distinct().collect(toList());拦截限制拦截流的前3个元素:Listresult=list.stream().limit(3).collect(toList());mappingmap对流中的每个元素执行一个函数,使元素转换为另一种类型的输出。stream会将每个元素发送给map函数,执行map中的Lambda表达式,最后将执行结果存储在一个新的stream中。比如获取每个人的姓名(其实就是将Human类型转成String类型):Listresult=list.stream().map(Human::getName).collect(toList());skipskip流的前3个元素:Listresult=list.stream().skip(3).collect(toList());mergeconcat合并2个元素:Listresult=Stream.concat(human1,human2).collect(toList());合并多个流示例:列出List中的不同单词,List集合如下:Listlist=newArrayList();list.add("zhong");list.add("ming");list.add("mao");思路如下:首先将list转为stream:list.stream();用空格分割单词:list.stream().map(line->line.split(""));分词后,每个元素就变成了一个String[]数组。将每个String[]转成一个流:list.stream().map(line->line.split("")).map(Arrays::stream)这时候大流包含小流,我们需要将这些小流合并为一个流。小流合并为大流:将之前的maplist.stream().map(line->line.split("")).flatMap(Arrays::stream)替换为flatMap去重list.stream().map(line->line.split("").flatMap(Arrays::stream).distinct().collect(toList());是否匹配任意元素:anyMatchanyMatch用于判断流中是否至少有一个元素满足指定条件,该判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。例如判断列表中是否有学生:booleanresult=list.stream().anyMatch(Man::isStudent);是否匹配所有元素:allMatchallMatch用于判断流中的所有元素是否满足指定条件,并将该条件通过Lambda表达式传递给anyMatch,执行结果为boolean。例如判断是否所有人都是学生:booleanresult=list.stream().allMatch(Man::isStudent);是否所有元素都不匹配:noneMatchnoneMatch与allMatch相反,用于判断流中所有元素是否都满足指定条件:booleanresult=list.stream().noneMatch(Man::isStudent);获取任意元素findAnyfindAny可以从流中随机选择一个元素,返回一个Optional类型的元素。可选的<人类>human=list.stream().findAny();获取第一个元素findFirstOptionalhuman=list.stream().findFirst();ReductionReduction是对集合中的所有元素进行指定操作,折叠成一个元素输出,比如:求最大值,求平均值等,这些操作就是将一个集合的元素折叠成一个元素输出。在流中,reduce函数可以执行归约。reduce函数接收两个参数:归约运算的Lambda表达式元素求和的初始值:自定义Lambda表达式实现求和示例:计算所有人的年龄总和intage=list.stream().reduce(0,(human1,human2)->human1.getAge()+human2.getAge());reduce第一个参数表示初始测试值为0;reduce的第二个参数是需要进行的归约操作,它接收一个有两个参数的Lambda表达式,reduce会把流中的元素两两输出到Lambda表达式中,最后计算累加和。元素求和:使用Integer.sum函数求和。在上面的方法中,我们定义了Lambda表达式来实现求和运算。如果当前流的元素是数值类型,我们可以使用Integer提供的sum函数来代替自定义的Lambda表达式。公式,如:intage=list.stream().reduce(0,Integer::sum);Integer类还提供了min、max等一系列数值运算,当流中的元素为数值类型时可以直接使用。使用reduce的数值流进行数值运算涉及到基本数值类型和引用数值类型之间的装箱和拆箱操作,效率较低。当流操作是纯数值操作时,使用数值流可以获得更高的效率。将普通流转换为数值流StreamAPI提供了三种数值流:IntStream、DoubleStream和LongStream,还提供了三种将普通流转换为数值流的方法:mapToInt、mapToDouble和mapToLong。例如,将Human中的年龄转换为数值流:IntStreamstream=list.stream().mapToInt(Human::getAge);数值计算每个数值流都提供了数值计算函数,例如max、min、sum等。例如求最大年龄:OptionalIntmaxAge=list.stream().mapToInt(Human::getAge).max();由于数值流可能为空,对于空的数值流计算最大值是没有意义的,因此max函数返回OptionalInt,它是Optional的子类,可以判断流是否为空,并处理它相应地。另外,mapToInt、mapToDouble、mapToLong执行数值运算后的返回结果为:OptionalInt、OptionalDouble、OptionalLong中间操作和集合操作类型返回类型/函数式接口函数描述符过滤中间StreamPredicateT->booleandistinct中间Stream跳过中间Streamlongmap中间StreamFunctionT->RflatMap中间StreamFunction>T->StreamlimitintermediateStreamlongsortedintermediateStreamComparator(T,T)->intanyMatch终端booleanPredicateT->booleannoneMatch终端booleanPredicateT->booleanallMatch终端booleanPredicateT->布尔值findAnyterminalOptionalfindFirstterminalOptionalforEachterminalvoidConsumerT->voidcollectterminalRCollectorreduceterminalOptionalBinaryOperator(T,T)->tcountterminallongCollector收集器用于对过滤和映射的流进行定型,使得最终的结果能够以不同的形式展示。collect方法是收集器,接收Collector接口的实现作为具体收集器的收集方法。Collector接口提供了很多默认的实现方法,我们可以直接使用它们来格式化流结果;我们也可以自定义Collector接口的实现来自定义自己的收集器。归约流由元素组成。归约就是将每个元素“折叠”成一个值。比如求和、最大值、平均值都是归约运算。一般归约如果需要自定义一个归约操作,需要使用Collectors.reducing函数,它接收三个参数:第一个参数是归约的初始值,第二个参数是归约操作的字段第三个参数正在总结归约操作的过程。Collectors类提供了一个专门用于汇总的工厂方法:Collectors.summingInt。它需要一个将对象映射到求和所需的整数的函数,并返回一个收集器;这个收集器,当传递给普通的收集方法时,执行我们需要的聚合。分组数据分组是拆分数据的一种更自然的操作。分组就是将流中的元素按照指定的类别进行划分,类似于SQL语句中的GROUPBY。多级分组多级分组可以支持在完成一个分组后对每个组单独进行分组。多级分组是使用带有两个参数的重载方法groupingBy实现的。第一个参数:一级分组的条件第二个参数:一个新的groupingBy函数,包含二级分组的条件Collectors类的静态工厂方法工厂方法的返回类型使用示例toListList将所有项目放入流中CollectaListListprojects=projectStream.collect(toList());toSetSet将流中的所有项目收集到一个Set中,删除重复项Setprojects=projectStream.collect(toSet());toCollectionCollection将流中的所有项目收集到由给定供应源创建的集合中Collectionprojects=projectStream.collect(toCollection(),ArrayList::new);countingLong统计流long中的元素个数howManyProjects=projectStream.collect(counting());summingIntInteger对流中项目的整数属性求和inttotalStars=projectStream.collect(summingInt(Project::getStars));averagingIntDouble计算流中项目的Integer属性的平均值doubleavgStars=projectStream。收集(averagingInt(项目::getStars));summarizingIntIntSummaryStatistics收集关于流中项目的Integer属性的统计信息,例如最大值、最小值、总和和平均值流StringshortProject=projectStream.map(Project::getName).collect(joining(","));maxByOptional可选的由给定比较器选择的最大元素,或者Optional.empty()如果流为空fattest=projectStream.collect(maxBy(comparingInt(Project::getStars)));minByOptional由给定比较器选择的最小元素的可选,如果流为空,则为Optional.empty()Optionalfattest=projectStream.collect(minBy(comparingInt(Project::getStars)));reducing操作生成的类型从一个初始值开始作为累加器,使用BinaryOperator将流中的元素逐一组合,从而将流reduce为单个值inttotalStars=projectStream.collect(reducing(0,Project::getStars,整数::总和));collectingAndThen转换函数返回的类型包含另一个收集器,转换函数inthowManyProjects应用到该收集器=projectStream.collect(collectingAndThen(toList(),List::size));groupingByMap>将stream中的item按照item的某个属性的值进行分组,将该属性值作为结果Map的keyMap>projectByLanguage=projectStream.收集(groupingBy(项目::getLanguage));partitioningByMap>根据对流Map>vegetarianDishes=projectStream.collect(partitioningBy(Project::isVegetarian));转换类型有一些可以生成其他集合的收集器,比如之前看到的toList,生成java.util.List类的实例。还有toSet和toCollection,它们分别生成Set和Collection类的实例。到目前为止,我已经讲了很多关于流的链式操作,但是总有一些时候需要最终生成一个集合——例如:现有的代码是为集合写的,所以需要将流转换成一个集合;对集合进行一系列的链式操作后,你终于希望产生一个值;在编写单元测试时,您需要对特定集合进行断言。使用toCollection通过自定义集合stream.collect(toCollection(TreeSet::new))收集元素;也可以使用收集器让流产生一个值。maxBy和minBy允许用户以某种特定顺序生成值。数据分区分区是分组的一种特殊情况:使用谓词(返回布尔值的函数)作为分类函数,称为分区函数。partition函数返回一个Boolean值,也就是说得到的分组Map的key类型是Boolean,所以最多可以分为两组:true是一组,false是一组。分区的好处是它保留了两组流元素列表,分区函数返回true或false。并行流并行流是一种将内容分成块并使用不同线程分别处理每个块的流。最后合并每个数据块的计算结果。要将顺序执行的流转换为并发流,只需调用parallel()方法publicstaticlongparallelSum(longn){returnStream.iterate(1L,i->i+1).limit(n).parallel().reduce(0L,Long::sum);}要将并发流转换为顺序流,只需调用sequential()方法stream.parallel().filter(...).sequential().map(...).并行()。减少();这两个方法可以调用多次,只有最后一次调用才决定流是顺序的还是并发的。并发流使用的默认线程数等于您机器的处理器核心数。这个值可以通过这个方法修改,它是一个全局属性。System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");并不是说使用多线程并行流处理数据的性能就一定要高于单线程顺序流,因为性能受到很多因素的影响。关于如何有效使用并发流的一些建议:如果您不确定,请自己测试。尽量使用基本流类型IntStream、LongStream、DoubleStream。一些使用并发流的操作性能比顺序流差,比如limit和findFirst。依赖于元素顺序的操作在并发流中非常耗费性能。findAny的性能会好很多,因为它不依赖于顺序。考虑流中计算性能(Q)和运算性能(N)的比较。Q表示单个进程需要的时间,N表示需要处理的进程数。如果Q的值越大,使用并发流的性能就会越高。高的。当数据量不大时,使用并发流,性能不会有提升。考虑数据结构:并发流需要对数据进行分解,不同的数据结构在分解时有不同的表现。流数据源和可分解性源可分解性ArrayList非常好。链表很差。IntStream.range非常好。Stream.iterate很差。哈希集很好。树集很好。流的特性和中间操作对流的修改都会影响数据分解的性能。例如,一个固定大小的流在分解任务时可以均匀分布,但如果有过滤操作,流无法提前知道这次操作后还剩下多少元素。考虑终端操作的性能:如果合并并发流的计算结果时终端操作的性能消耗过高,那么使用并发流的性能提升将得不偿失。