5万字:Stream和Lambda表达式最佳实践2

7.2处理checkedException
checkedException是必须要处理的异常 , 我们还是看个例子:staticvoidthrowIOException(Integerinteger)throwsIOException{}复制代码List
integers=Arrays.asList(1,2,3,4,5);integers.forEach(i->throwIOException(i));复制代码
上面我们定义了一个方法抛出IOException , 这是一个checkedException , 需要被处理 , 所以在下面的forEach中 , 程序会编译失败 , 因为没有处理相应的异常 。
最简单的办法就是try , catch住 , 如下所示:integers.forEach(i->{try{throwIOException(i);}catch(IOExceptione){thrownewRuntimeException(e);}});复制代码
当然 , 这样的做法的坏处我们在上面已经讲过了 , 同样的 , 我们可以定义一个新的wrapper方法:staticConsumerconsumerWrapper(ThrowingConsumer
throwingConsumer){returni->{try{throwingConsumer.accept(i);}catch(Exceptionex){thrownewRuntimeException(ex);}};}复制代码
我们这样调用:integers.forEach(consumerWrapper(i->throwIOException(i)));复制代码
我们也可以封装一下异常:static
ConsumerconsumerWrapperWithExceptionClass(ThrowingConsumer
throwingConsumer,Class
{try{throwingConsumer.accept(i);}catch(Exceptionex){try{EexCast=exceptionClass.cast(ex);System.err.println("Exceptionoccured:"+exCast.getMessage());}catch(ClassCastExceptionccEx){thrownewRuntimeException(ex);}}};}复制代码
然后这样调用:integers.forEach(consumerWrapperWithExceptionClass(i->throwIOException(i),IOException.class));复制代码8.stream中throwException
之前的文章我们讲到 , 在stream中处理异常 , 需要将checkedexception转换为uncheckedexception来处理 。
我们是这样做的:staticConsumerconsumerWrapper(ThrowingConsumer
throwingConsumer){returni->{try{throwingConsumer.accept(i);}catch(Exceptionex){thrownewRuntimeException(ex);}};}复制代码
将异常捕获 , 然后封装成为RuntimeException 。
封装成RuntimeException感觉总是有那么一点点问题 , 那么有没有什么更好的办法?8.1throw小诀窍
java的类型推断大家应该都知道 , 如果是这样的形式 , 那么T将会被认为是RuntimeException!
我们看下例子:publicclassRethrowException{publicstatic
RthrowException(Exceptiont)throwsT{throw(T)t;//justthrowit,convertcheckedexceptiontouncheckedexception}}复制代码
上面的类中 , 我们定义了一个throwException方法 , 接收一个Exception参数 , 将其转换为T , 这里的T就是uncheckedexception 。
接下来看下具体的使用:@Slf4jpublicclassRethrowUsage{publicstaticvoidmain(String[]args){try{throwIOException();}catch(IOExceptione){log.error(e.getMessage(),e);RethrowException.throwException(e);}}staticvoidthrowIOException()throwsIOException{thrownewIOException("ioexception");}}复制代码
上面的例子中 , 我们将一个IOException转换成了一个uncheckedexception 。 9.stream中Collectors的用法
在javastream中 , 我们通常需要将处理后的stream转换成集合类 , 这个时候就需要用到stream.collect方法 。 collect方法需要传入一个Collector类型 , 要实现Collector还是很麻烦的 , 需要实现好几个接口 。
于是java提供了更简单的Collectors工具类来方便我们构建Collector 。
下面我们将会具体讲解Collectors的用法 。
假如我们有这样两个list:List
list=Arrays.asList("jack","bob","alice","mark");List
duplicateList=Arrays.asList("jack","jack","alice","mark");复制代码
上面一个是无重复的list , 一个是带重复数据的list 。 接下来的例子我们会用上面的两个list来讲解Collectors的用法 。 9.1Collectors.toList()List
listResult=list.stream().collect(Collectors.toList());log.info("{}",listResult);复制代码
将stream转换为list 。 这里转换的list是ArrayList , 如果想要转换成特定的list , 需要使用toCollection方法 。 9.2Collectors.toSet()Set
setResult=list.stream().collect(Collectors.toSet());log.info("{}",setResult);复制代码
toSet将Stream转换成为set 。 这里转换的是HashSet 。 如果需要特别指定set , 那么需要使用toCollection方法 。
因为set中是没有重复的元素 , 如果我们使用duplicateList来转换的话 , 会发现最终结果中只有一个jack 。 Set
duplicateSetResult=duplicateList.stream().collect(Collectors.toSet());log.info("{}",duplicateSetResult);复制代码9.3Collectors.toCollection()
上面的toMap,toSet转换出来的都是特定的类型 , 如果我们需要自定义 , 则可以使用toCollection()List
custListResult=list.stream().collect(Collectors.toCollection(LinkedList::new));log.info("{}",custListResult);复制代码
上面的例子 , 我们转换成了LinkedList 。 9.4Collectors.toMap()
toMap接收两个参数 , 第一个参数是keyMapper , 第二个参数是valueMapper:Map
mapResult=list.stream().collect(Collectors.toMap(Function.identity(),String::length));log.info("{}",mapResult);复制代码
如果stream中有重复的值 , 则转换会报IllegalStateException异常:Map
duplicateMapResult=duplicateList.stream().collect(Collectors.toMap(Function.identity(),String::length));复制代码
怎么解决这个问题呢?我们可以这样:Map
duplicateMapResult2=duplicateList.stream().collect(Collectors.toMap(Function.identity(),String::length,(item,identicalItem)->item));log.info("{}",duplicateMapResult2);复制代码
在toMap中添加第三个参数mergeFunction , 来解决冲突的问题 。 9.5Collectors.collectingAndThen()
collectingAndThen允许我们对生成的集合再做一次操作 。 List
collectAndThenResult=list.stream().collect(Collectors.collectingAndThen(Collectors.toList(),l->{returnnewArrayList<>(l);}));log.info("{}",collectAndThenResult);复制代码9.6Collectors.joining()
Joining用来连接stream中的元素:StringjoinResult=list.stream().collect(Collectors.joining());log.info("{}",joinResult);StringjoinResult1=list.stream().collect(Collectors.joining(""));log.info("{}",joinResult1);StringjoinResult2=list.stream().collect(Collectors.joining("","prefix","suffix"));log.info("{}",joinResult2);复制代码
可以不带参数 , 也可以带一个参数 , 也可以带三个参数 , 根据我们的需要进行选择 。 9.7Collectors.counting()
counting主要用来统计stream中元素的个数:LongcountResult=list.stream().collect(Collectors.counting());log.info("{}",countResult);复制代码9.8Collectors.summarizingDouble/Long/Int()
SummarizingDouble/Long/Int为stream中的元素生成了统计信息 , 返回的结果是一个统计类:IntSummaryStatisticsintResult=list.stream().collect(Collectors.summarizingInt(String::length));log.info("{}",intResult);复制代码
输出结果:22:22:35.238[main]INFOcom.flydean.CollectorUsage-IntSummaryStatistics{count=4,sum=16,min=3,average=4.000000,max=5}复制代码9.9Collectors.averagingDouble/Long/Int()
averagingDouble/Long/Int()对stream中的元素做平均:DoubleaverageResult=list.stream().collect(Collectors.averagingInt(String::length));log.info("{}",averageResult);复制代码9.10Collectors.summingDouble/Long/Int()
summingDouble/Long/Int()对stream中的元素做sum操作:DoublesummingResult=list.stream().collect(Collectors.summingDouble(String::length));log.info("{}",summingResult);复制代码9.11Collectors.maxBy()/minBy()
maxBy()/minBy()根据提供的Comparator , 返回stream中的最大或者最小值:Optional
maxByResult=list.stream().collect(Collectors.maxBy(Comparator.naturalOrder()));log.info("{}",maxByResult);复制代码9.12Collectors.groupingBy()
GroupingBy根据某些属性进行分组 , 并返回一个Map:Map>groupByResult=list.stream().collect(Collectors.groupingBy(String::length,Collectors.toSet()));log.info("{}",groupByResult);复制代码9.13Collectors.partitioningBy()
PartitioningBy是一个特别的groupingBy , PartitioningBy返回一个Map , 这个Map是以boolean值为key , 从而将stream分成两部分 , 一部分是匹配PartitioningBy条件的 , 一部分是不满足条件的:Map>partitionResult=list.stream().collect(Collectors.partitioningBy(s->s.length()>3));log.info("{}",partitionResult);复制代码
看下运行结果:22:39:37.082[main]INFOcom.flydean.CollectorUsage-{false=[bob],true=[jack,alice,mark]}复制代码
结果被分成了两部分 。 10.创建一个自定义的collector
在之前的javacollectors文章里面 , 我们讲到了stream的collect方法可以调用Collectors里面的toList()或者toMap()方法 , 将结果转换为特定的集合类 。
今天我们介绍一下怎么自定义一个Collector 。 10.1Collector介绍
我们先看一下Collector的定义:
5万字:Stream和Lambda表达式最佳实践2
文章图片

Collector接口需要实现supplier(),accumulator(),combiner(),finisher(),characteristics()这5个接口 。
同时Collector也提供了两个静态of方法来方便我们创建一个Collector实例 。
我们可以看到两个方法的参数跟Collector接口需要实现的接口是一一对应的 。
下面分别解释一下这几个参数:supplier
Supplier是一个函数 , 用来创建一个新的可变的集合 。 换句话说Supplier用来创建一个初始的集合 。 accumulator

accumulator定义了累加器 , 用来将原始元素添加到集合中 。 combiner
combiner用来将两个集合合并成一个 。 finisher
finisher将集合转换为最终的集合类型 。 characteristics
characteristics表示该集合的特征 。 这个不是必须的参数 。
Collector定义了三个参数类型 , T是输入元素的类型 , A是reductionoperation的累加类型也就是Supplier的初始类型 , R是最终的返回类型 。 我们画个图来看一下这些类型之间的转换关系:
5万字:Stream和Lambda表达式最佳实践2
文章图片
有了这几个参数 , 我们接下来看看怎么使用这些参数来构造一个自定义Collector 。 10.2自定义Collector
我们利用Collector的of方法来创建一个不变的Set:publicstaticCollector,Set
toImmutableSet(){returnCollector.of(HashSet::new,Set::add,(left,right)->{left.addAll(right);returnleft;},Collections::unmodifiableSet);}复制代码
上面的例子中 , 我们HashSet::new作为supplier , Set::add作为accumulator , 自定义了一个方法作为combiner , 最后使用Collections::unmodifiableSet将集合转换成不可变集合 。
上面我们固定使用HashSet::new作为初始集合的生成方法 , 实际上 , 上面的方法可以更加通用:publicstaticCollectortoImmutableSet(Supplier
{left.addAll(right);returnleft;},Collections::unmodifiableSet);}复制代码
上面的方法 , 我们将supplier提出来作为一个参数 , 由外部来定义 。


看下上面两个方法的测试:@TestpublicvoidtoImmutableSetUsage(){Set
stringSet1=Stream.of("a","b","c","d").collect(ImmutableSetCollector.toImmutableSet());log.info("{}",stringSet1);Set
stringSet2=Stream.of("a","b","c","d").collect(ImmutableSetCollector.toImmutableSet(LinkedHashSet::new));log.info("{}",stringSet2);}复制代码
输出:INFOcom.flydean.ImmutableSetCollector-[a,b,c,d]INFOcom.flydean.ImmutableSetCollector-[a,b,c,d]复制代码11.streamreduce详解和误区
【5万字:Stream和Lambda表达式最佳实践2】StreamAPI提供了一些预定义的reduce操作 , 比如count(),max(),min(),sum()等 。 如果我们需要自己写reduce的逻辑 , 则可以使用reduce方法 。
本文将会详细分析一下reduce方法的使用 , 并给出具体的例子 。 11.1reduce详解
Stream类中有三种reduce , 分别接受1个参数 , 2个参数 , 和3个参数 , 首先来看一个参数的情况:Optionalreduce(BinaryOperatoraccumulator);复制代码
该方法接受一个BinaryOperator参数 , BinaryOperator是一个@FunctionalInterface,需要实现方法:Rapply(Tt,Uu);复制代码
accumulator告诉reduce方法怎么去累计stream中的数据 。
举个例子:List
intList=Arrays.asList(1,2,3);Optional
result1=intList.stream().reduce(Integer::sum);log.info("{}",result1);复制代码
上面的例子输出结果:com.flydean.ReduceUsage-Optional[6]复制代码
一个参数的例子很简单 。 这里不再多说 。
接下来我们再看一下两个参数的例子:Treduce(Tidentity,BinaryOperatoraccumulator);复制代码
这个方法接收两个参数:identity和accumulator 。 多出了一个参数identity 。
也许在有些文章里面有人告诉你identity是reduce的初始化值 , 可以随便指定 , 如下所示:Integerresult2=intList.stream().reduce(100,Integer::sum);log.info("{}",result2);复制代码
上面的例子 , 我们计算的值是106 。
如果我们将stream改成parallelStream:Integerresult3=intList.parallelStream().reduce(100,Integer::sum);log.info("{}",result3);复制代码
得出的结果就是306 。
为什么是306呢?因为在并行计算的时候 , 每个线程的初始累加值都是100 , 最后3个线程加出来的结果就是306 。
并行计算和非并行计算的结果居然不一样 , 这肯定不是JDK的问题 , 我们再看一下JDK中对identity的说明:identity必须是accumulator函数的一个identity , 也就是说必须满足:对于所有的t,都必须满足accumulator.apply(identity,t)==t
所以这里我们传入100是不对的 , 因为sum(100+1)!=1 。
这里sum方法的identity只能是0 。
如果我们用0作为identity,则stream和parallelStream计算出的结果是一样的 。 这就是identity的真正意图 。
下面再看一下三个参数的方法:Ureduce(Uidentity,BiFunction
accumulator,BinaryOperatorcombiner);复制代码
和前面的方法不同的是 , 多了一个combiner , 这个combiner用来合并多线程计算的结果 。 同样的 , identity需要满足combiner.apply(u,accumulator.apply(identity,t))==accumulator.apply(u,t)
大家可能注意到了为什么accumulator的类型是BiFunction而combiner的类型是BinaryOperator?publicinterfaceBinaryOperatorextendsBiFunction
复制代码
BinaryOperator是BiFunction的子接口 。 BiFunction中定义了要实现的apply方法 。
其实reduce底层方法的实现只用到了apply方法 , 并没有用到接口中其他的方法 , 所以我猜测这里的不同只是为了简单的区分 。
虽然reduce是一个很常用的方法 , 但是大家一定要遵循identity的规范 , 并不是所有的identity都是合适的 。 12.stream中的Spliterator
Spliterator是在java8引入的一个接口 , 它通常和stream一起使用 , 用来遍历和分割序列 。
只要用到stream的地方都需要Spliterator , 比如List , Collection , IOchannel等等 。
我们先看一下Collection中stream方法的定义:defaultStreamstream(){returnStreamSupport.stream(spliterator(),false);}复制代码defaultStreamparallelStream(){returnStreamSupport.stream(spliterator(),true);}复制代码
我们可以看到 , 不管是并行stream还是非并行stream , 都是通过StreamSupport来构造的 , 并且都需要传入一个spliterator的参数 。
好了 , 我们知道了spliterator是做什么的之后 , 看一下它的具体结构:
5万字:Stream和Lambda表达式最佳实践2
文章图片
spliterator有四个必须实现的方法 , 我们接下来进行详细的讲解 。 12.1tryAdvance
tryAdvance就是对stream中的元素进行处理的方法 , 如果元素存在 , 则对他进行处理 , 并返回true , 否则返回false 。
如果我们不想处理stream后续的元素 , 则在tryAdvance中返回false即可 , 利用这个特征 , 我们可以中断stream的处理 。 这个例子我将会在后面的文章中讲到 。 12.2trySplit
trySplit尝试对现有的stream进行分拆 , 一般用在parallelStream的情况 , 因为在并发stream下 , 我们需要用多线程去处理stream的不同元素 , trySplit就是对stream中元素进行分拆处理的方法 。
理想情况下trySplit应该将stream拆分成数目相同的两部分才能最大提升性能 。 12.3estimateSize
estimateSize表示Spliterator中待处理的元素 , 在trySplit之前和之后一般是不同的 , 后面我们会在具体的例子中说明 。 12.4characteristics
characteristics表示这个Spliterator的特征 , Spliterator有8大特征:publicstaticfinalintORDERED=0x00000010;//表示元素是有序的(每一次遍历结果相同)publicstaticfinalintDISTINCT=0x00000001;//表示元素不重复publicstaticfinalintSORTED=0x00000004;//表示元素是按一定规律进行排列(有指定比较器)publicstaticfinalintSIZED=0x00000040;//表示大小是固定的publicstaticfinalintNONNULL=0x00000100;//表示没有null元素publicstaticfinalintIMMUTABLE=0x00000400;//表示元素不可变publicstaticfinalintCONCURRENT=0x00001000;//表示迭代器可以多线程操作publicstaticfinalintSUBSIZED=0x00004000;//表示子Spliterators都具有SIZED特性复制代码
一个Spliterator可以有多个特征 , 多个特征进行or运算 , 最后得到最终的characteristics 。 12.5举个例子
上面我们讨论了Spliterator一些关键方法 , 现在我们举一个具体的例子:@AllArgsConstructor@DatapublicclassCustBook{privateStringname;}复制代码
先定义一个CustBook类 , 里面放一个name变量 。
定义一个方法 , 来生成一个CustBook的list:publicstaticList
generateElements(){returnStream.generate(()->newCustBook("custbook")).limit(1000).collect(Collectors.toList());}复制代码
我们定义一个call方法 , 在call方法中调用了tryAdvance方法 , 传入了我们自定义的处理方法 。 这里我们修改book的name,并附加额外的信息 。 publicStringcall(Spliterator
spliterator){intcurrent=0;while(spliterator.tryAdvance(a->a.setName("testname".concat("-addnewname")))){current++;}returnThread.currentThread().getName()+":"+current;}复制代码
最后 , 写一下测试方法:@TestpublicvoiduseTrySplit(){Spliterator
split1=SpliteratorUsage.generateElements().spliterator();Spliterator
split2=split1.trySplit();log.info("beforetryAdvance:{}",split1.estimateSize());log.info("Characteristics{}",split1.characteristics());log.info(call(split1));log.info(call(split2));log.info("aftertryAdvance{}",split1.estimateSize());}复制代码
运行的结果如下:23:10:08.852[main]INFOcom.flydean.SpliteratorUsage-beforetryAdvance:50023:10:08.857[main]INFOcom.flydean.SpliteratorUsage-Characteristics1646423:10:08.858[main]INFOcom.flydean.SpliteratorUsage-main:50023:10:08.858[main]INFOcom.flydean.SpliteratorUsage-main:50023:10:08.858[main]INFOcom.flydean.SpliteratorUsage-aftertryAdvance0复制代码
List总共有1000条数据 , 调用一次trySplit之后 , 将List分成了两部分 , 每部分500条数据 。
注意 , 在tryAdvance调用之后 , estimateSize变为0 , 表示所有的元素都已经被处理完毕 。
再看一下这个Characteristics=16464 , 转换为16进制:Ox4050=ORDEREDorSIZEDorSUBSIZED这三个的或运算 。
这也是ArrayList的基本特征 。 13.breakstream的foreach
我们通常需要在javastream中遍历处理里面的数据 , 其中foreach是最最常用的方法 。
但是有时候我们并不想处理完所有的数据 , 或者有时候Stream可能非常的长 , 或者根本就是无限的 。
一种方法是先filter出我们需要处理的数据 , 然后再foreach遍历 。
那么我们如何直接break这个stream呢?今天本文重点讲解一下这个问题 。 13.1使用Spliterator
上篇文章我们在讲Spliterator的时候提到了 , 在tryAdvance方法中 , 如果返回false , 则Spliterator将会停止处理后续的元素 。
通过这个思路 , 我们可以创建自定义Spliterator 。
假如我们有这样一个stream:Stream
ints=Stream.of(1,2,3,4,5,6,7,8,9,10);复制代码
我们想定义一个操作 , 当x>5的时候就停止 。
我们定义一个通用的Spliterator:publicclassCustomSpliteratorextendsSpliterators.AbstractSpliterator{privateSpliteratorsplitr;privatePredicatepredicate;privatevolatilebooleanisMatched=true;publicCustomSpliterator(Spliteratorsplitr,Predicatepredicate){super(splitr.estimateSize(),0);this.splitr=splitr;this.predicate=predicate;}@OverridepublicsynchronizedbooleantryAdvance(Consumer
consumer){booleanhadNext=splitr.tryAdvance(elem->{if(predicate.test(elem)&&isMatched){consumer.accept(elem);}else{isMatched=false;}});returnhadNext&&isMatched;}}复制代码
在上面的类中 , predicate是我们将要传入的判断条件 , 我们重写了tryAdvance , 通过将predicate.test(elem)加入判断条件 , 从而当条件不满足的时候返回false.
看下怎么使用:@Slf4jpublicclassCustomSpliteratorUsage{publicstaticStreamtakeWhile(Streamstream,Predicatepredicate){CustomSpliterator
(stream.spliterator(),predicate);returnStreamSupport.stream(customSpliterator,false);}publicstaticvoidmain(String[]args){Stream
ints=Stream.of(1,2,3,4,5,6,7,8,9,10);List
result=takeWhile(ints,x->x<5).collect(Collectors.toList());log.info(result.toString());}}复制代码
我们定义了一个takeWhile方法 , 接收Stream和predicate条件 。
只有当predicate条件满足的时候才会继续 , 我们看下输出的结果:[main]INFOcom.flydean.CustomSpliteratorUsage-[1,2,3,4]复制代码13.2自定义forEach方法
除了使用Spliterator , 我们还可以自定义forEach方法来使用自己的遍历逻辑:publicclassCustomForEach{publicstaticclassBreaker{privatevolatilebooleanshouldBreak=false;publicvoidstop(){shouldBreak=true;}booleanget(){returnshouldBreak;}}publicstaticvoidforEach(Streamstream,BiConsumer
consumer){Spliterator
{consumer.accept(elem,breaker);});}}}复制代码
上面的例子中 , 我们在forEach中引入了一个外部变量 , 通过判断这个外部变量来决定是否进入spliterator.tryAdvance方法 。
看下怎么使用:@Slf4jpublicclassCustomForEachUsage{publicstaticvoidmain(String[]args){Stream
ints=Stream.of(1,2,3,4,5,6,7,8,9,10);List
result=newArrayList
{if(elem>=5){breaker.stop();}else{result.add(elem);}});log.info(result.toString());}}复制代码
上面我们用新的forEach方法 , 并通过判断条件来重置判断flag , 从而达到breakstream的目的 。 14.predicatechain的使用
Predicate是一个FunctionalInterface , 代表的方法需要输入一个参数 , 返回boolean类型 。 通常用在stream的filter中 , 表示是否满足过滤条件 。 booleantest(Tt);复制代码14.1基本使用
我们先看下在stream的filter中怎么使用Predicate:@TestpublicvoidbasicUsage(){List
stringList=Stream.of("a","b","c","d").filter(s->s.startsWith("a")).collect(Collectors.toList());log.info("{}",stringList);}复制代码
上面的例子很基础了 , 这里就不多讲了 。 14.2使用多个Filter
如果我们有多个Predicate条件 , 则可以使用多个filter来进行过滤:publicvoidmultipleFilters(){List
stringList=Stream.of("a","ab","aac","ad").filter(s->s.startsWith("a")).filter(s->s.length()>1).collect(Collectors.toList());log.info("{}",stringList);}复制代码
上面的例子中 , 我们又添加了一个filter , 在filter又添加了一个Predicate 。 14.3使用复合Predicate
Predicate的定义是输入一个参数 , 返回boolean值 , 那么如果有多个测试条件 , 我们可以将其合并成一个test方法:@TestpublicvoidcomplexPredicate(){List
stringList=Stream.of("a","ab","aac","ad").filter(s->s.startsWith("a")&&s.length()>1).collect(Collectors.toList());log.info("{}",stringList);}复制代码
上面的例子中 , 我们把s.startsWith("a")&&s.length()>1作为test的实现 。 14.4组合Predicate
Predicate虽然是一个interface , 但是它有几个默认的方法可以用来实现Predicate之间的组合操作 。
比如:Predicate.and(),Predicate.or(),和Predicate.negate() 。
下面看下他们的例子:@TestpublicvoidcombiningPredicate(){Predicate
predicate1=s->s.startsWith("a");Predicate
predicate2=s->s.length()>1;List
stringList1=Stream.of("a","ab","aac","ad").filter(predicate1.and(predicate2)).collect(Collectors.toList());log.info("{}",stringList1);List
stringList2=Stream.of("a","ab","aac","ad").filter(predicate1.or(predicate2)).collect(Collectors.toList());log.info("{}",stringList2);List
stringList3=Stream.of("a","ab","aac","ad").filter(predicate1.or(predicate2.negate())).collect(Collectors.toList());log.info("{}",stringList3);}复制代码
实际上 , 我们并不需要显示的assign一个predicate , 只要是满足predicate接口的lambda表达式都可以看做是一个predicate 。 同样可以调用and , or和negate操作:List
stringList4=Stream.of("a","ab","aac","ad").filter(((Predicate
)a->a.startsWith("a")).and(a->a.length()>1)).collect(Collectors.toList());log.info("{}",stringList4);复制代码14.5Predicate的集合操作
如果我们有一个Predicate集合 , 我们可以使用reduce方法来对其进行合并运算:@TestpublicvoidcombiningPredicateCollection(){List>allPredicates=newArrayList
a.startsWith("a"));allPredicates.add(a->a.length()>1);List
stringList=Stream.of("a","ab","aac","ad").filter(allPredicates.stream().reduce(x->true,Predicate::and)).collect(Collectors.toList());log.info("{}",stringList);}复制代码
上面的例子中 , 我们调用reduce方法 , 对集合中的Predicate进行了and操作 。 15.中构建无限的stream
在java中 , 我们可以将特定的集合转换成为stream , 那么在有些情况下 , 比如测试环境中 , 我们需要构造一定数量元素的stream , 需要怎么处理呢?
这里我们可以构建一个无限的stream , 然后调用limit方法来限定返回的数目 。 15.1基本使用
先看一个使用Stream.iterate来创建无限Stream的例子:@TestpublicvoidinfiniteStream(){Stream
infiniteStream=Stream.iterate(0,i->i+1);List

collect=infiniteStream.limit(10).collect(Collectors.toList());log.info("{}",collect);}复制代码
上面的例子中 , 我们通过调用Stream.iterate方法 , 创建了一个0 , 1 , 2 , 3 , 4....的无限stream 。
然后调用limit(10)来获取其中的前10个 。 最后调用collect方法将其转换成为一个集合 。
看下输出结果:INFOcom.flydean.InfiniteStreamUsage-[0,1,2,3,4,5,6,7,8,9]复制代码15.2自定义类型
如果我们想输出自定义类型的集合 , 该怎么处理呢?
首先 , 我们定义一个自定义类型:@Data@AllArgsConstructorpublicclassIntegerWrapper{privateIntegerinteger;}复制代码
然后利用Stream.generate的生成器来创建这个自定义类型:publicstaticIntegerWrappergenerateCustType(){returnnewIntegerWrapper(newRandom().nextInt(100));}@TestpublicvoidinfiniteCustType(){Supplier
randomCustTypeSupplier=InfiniteStreamUsage::generateCustType;Stream
infiniteStreamOfCustType=Stream.generate(randomCustTypeSupplier);List
collect=infiniteStreamOfCustType.skip(10).limit(10).collect(Collectors.toList());log.info("{}",collect);}复制代码
看下输出结果:INFOcom.flydean.InfiniteStreamUsage-[IntegerWrapper(integer=46),IntegerWrapper(integer=42),IntegerWrapper(integer=67),IntegerWrapper(integer=11),IntegerWrapper(integer=14),IntegerWrapper(integer=80),IntegerWrapper(integer=15),IntegerWrapper(integer=19),IntegerWrapper(integer=72),IntegerWrapper(integer=41)]复制代码16.自定义parallelStream的threadpool
之前我们讲到parallelStream的底层使用到了ForkJoinPool来提交任务的 , 默认情况下ForkJoinPool为每一个处理器创建一个线程 , parallelStream如果没有特别指明的情况下 , 都会使用这个共享线程池来提交任务 。
那么在特定的情况下 , 我们想使用自定义的ForkJoinPool该怎么处理呢?16.1通常操作
假如我们想做一个从1到1000的加法 , 我们可以用并行stream这样做:List
integerList=IntStream.range(1,1000).boxed().collect(Collectors.toList());ForkJoinPoolcustomThreadPool=newForkJoinPool(4);Integertotal=integerList.parallelStream().reduce(0,Integer::sum);log.info("{}",total);复制代码
输出结果:INFOcom.flydean.CustThreadPool-499500复制代码16.2使用自定义ForkJoinPool
上面的例子使用的共享的threadpool 。 我们看下怎么使用自定义的threadpool来提交并行stream:List
integerList=IntStream.range(1,1000).boxed().collect(Collectors.toList());ForkJoinPoolcustomThreadPool=newForkJoinPool(4);IntegeractualTotal=customThreadPool.submit(()->integerList.parallelStream().reduce(0,Integer::sum)).get();log.info("{}",actualTotal);复制代码
上面的例子中 , 我们定义了一个4个线程的ForkJoinPool , 并使用它来提交了这个parallelStream 。
输出结果:INFOcom.flydean.CustThreadPool-499500复制代码
如果不想使用公共的线程池 , 则可以使用自定义的ForkJoinPool来提交 。 17.总结
本文统一介绍了Stream和lambda表达式的使用 , 涵盖了Stream和lambda表达式的各个小的细节 , 希望大家能够喜欢 。


    推荐阅读