Java8中stream和functional interface的配合使用详解
前言
Java8提供了一组称为stream的API,用于处理可遍历的流式数据。streamAPI的设计,充分融合了函数式编程的理念,极大简化了代码量。
大家其实可以把Stream当成一个高级版本的Iterator。原始版本的Iterator,用户只能一个一个的遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如“过滤掉长度大于10的字符串”、“获取每个字符串的首字母”等,具体这些操作如何应用到每个元素上,就给Stream就好了!(这个秘籍,一般人我不告诉他:))
我们来讲解如何将常用的streamAPI与相应的functionalinterface(函数式接口)配合使用,达成数据处理的目的。
类似于困扰哲学家们数千年的三大问题,关于stream我们也有三个疑团需要解开:它从哪里来?它能做什么?它会变成什么?
generate与Supplier
stream最常见的来源是Collection。Collection是一组可遍历元素的抽象容器。它有两大类实现:不允许重复元素的Set和允许重复的List。只要在某个Collection对象后面加上.stream()或者.parallelStream()就可以得到相应的stream了。
如果没有现成的Collection,或者Collection太大根本存不下,还有什么办法可以生成stream么?如果知道生成stream中每个元素的算法,就可以无中生有造出一个stream来。这里用到的是方法Stream.generate(),它依赖于一个函数式接口Supplier。
staticStream generate(Supplier s);
Supplier的方法get()在每次调用时都返回一个T的对象。因为get()方法不接收任何参数,所以使用generate时,代码总是会写成类似()->returnValue的样子。
另外,由于get()可以被调用无限多次,因此通过generate生成的stream也是无限长的,必要时可以通过.limit()截取前若干个元素。
例如,如果想获得一个无限长的随机UUID序列,可以使用下面的方法:
StreaminfiniteUUIDStream=Stream.generate(()->UUID.randomUUID());
想要获取诸如1~10这样的序列也是可行的,但需要一个helperclass记录当前状态,这里就不提供案例了。
forEach与Consumer
知道了如何生成stream,也要知道如何消费它。既然stream可以从Collection来,那么最后应该也能变成Collection,这就是collect()的功劳了。collect()接收一个Collector作为参数,返回从stream生成的Collection对象。不过这个Collector不是函数式接口,所以不属于本文的重点。下面着重讲解的是forEach方法。
voidforEach(Consumeraction);
forEach与函数式接口Consumer配合工作,Consumer的voidaccept(Tt)方法就是来消费stream中的各个元素的。因为accept接收单个元素T作为参数,forEach会写成e->statement的形式,其中statement不返回任何值。
比如,逐行打印stream中的每一个元素,就可以写作:
stream.forEach(e->System.out.println(e));
或者通过方法引用进一步简化:
stream.forEach(System.out::println);
reduce与BinaryOperator
除了forEach这种吞噬元素的终结型操作以外,使用stream中的元素还有两种常见的模式。第一种依旧是终结型操作:整合所有的元素,最后返回一个单一的值,我们把这个操作称作reduce。第二种则是过程性操作,它让每个元素都有自己对应的返回值,之后重组成为新的stream,以便下一步继续利用。我们把第二种操作称为map。把刚刚提及的这两个操作结合起来,就是大名鼎鼎的MapReduce了(误)。
reduce与一种特殊的函数式接口搭配使用,它叫BinaryOperator。BinaryOperator
讲解reduce时最常见的例子就是求一个stream中所有元素之和了:
//stream:StreamOptional sum=stream.reduce((a,b)->a+b);
我们可以看出,reduce方法的特征是(a,b)->returnValue。它返回的结果是Optional,我们可以用.isPresent()查看是否为空值;当值不为空时,用.get()获取数据。
map与Function
map或许是stream中使用最为广泛的一个操作了。与reduce涉及的BiFunction不同,与map配套使用的函数式接口是略为简单的Function。它同样是一个宽泛的函数式接口,同时也是函数式接口最著名的代表。Function
比如说,把一个stream中的每一个字符串都变成大写:
//original:StreamStream transformed=original.map(e->e.toUpperCase());
map方法的特征是e->returnValue。正如我们之前用过的System.out::println一样,这里也可以使用方法引用简化代码,只要引用的方法符合map预期的类型即可:传入一个T参数,返回一个R值。
//original:StreamStream transformed=original.map(String::toUpperCase);
filter与Predicate
介绍了forEach,reduce和map这些重量级的操作,下面我们来处理一个尴尬的问题:如果这个stream中有我们不想要的元素怎么办?答案是使用filter把他们踢出去。
与filter搭配使用的函数式接口是Predicate。Predicate
在下面的例子中,程序只打印stream中的偶数:
//stream:Streamstream.filter(e->e%2==0).forEach(System.out::println);
可以看出,由于Predicate是一种特异的Function,所以filter方法的特征与map在外观上如出一辙。不过filter要保证e->returnValue中的returnValue是一个boolean,否则编译会报错。
sorted与Comparator
最后来看看stream中非常强大的sorted方法,它允许我们自定义比较规则对stream中的元素排序。与sorted搭配的函数式接口是Comparator,Comparator
接下来的例子将stream中的浮点数按绝对值的升序排列,并打印出来:
//stream:Streamstream.sorted((a,b)->{ doublediff=a-b; if(diff<0)return-1; elseif(diff>0)return1; elsereturn0; }).forEach(System.out::println);
不难看出,sorted方法的特征与reduce比较相似,都是(a,b)->returnValue的结构,但是要保证returnValue是int类型。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。